diff --git a/.travis/before_install.sh b/.travis/before_install.sh index a054ed921..3295a938c 100755 --- a/.travis/before_install.sh +++ b/.travis/before_install.sh @@ -86,6 +86,19 @@ cd /tmp || die [ $BUILD_TARGET != posix-32-bit ] || { sudo apt-get install g++-multilib || die } + + [ $BUILD_TARGET != posix-distcheck ] || { + sudo apt-get install clang || die + } + + [ $BUILD_TARGET != posix -o $CC != clang ] || { + sudo apt-get install clang || die + } + + # Packages used by sniffer + sudo -H pip install pycryptodome==3.4.3 || die + pip install pycryptodome==3.4.3 || die + } [ $TRAVIS_OS_NAME != osx ] || { diff --git a/tests/scripts/Makefile.am b/tests/scripts/Makefile.am index a0b8fc4d9..4c6e78ee7 100644 --- a/tests/scripts/Makefile.am +++ b/tests/scripts/Makefile.am @@ -121,7 +121,17 @@ EXTRA_DIST = \ thread-cert/Cert_9_2_16_ActivePendingPartition.py \ thread-cert/Cert_9_2_17_Orphan.py \ thread-cert/Cert_9_2_18_RollBackActiveTimestamp.py \ + thread-cert/common.py \ + thread-cert/config.py \ + thread-cert/ipv6.py \ + thread-cert/lowpan.py \ + thread-cert/mac802154.py \ + thread-cert/message.py \ + thread-cert/mle.py \ + thread-cert/net_crypto.py \ + thread-cert/network_data.py \ thread-cert/node.py \ + thread-cert/sniffer.py \ $(NULL) if OPENTHREAD_BUILD_TESTS diff --git a/tests/scripts/thread-cert/Cert_5_1_01_RouterAttach.py b/tests/scripts/thread-cert/Cert_5_1_01_RouterAttach.py index 3b7e36052..aa6fdd55d 100755 --- a/tests/scripts/thread-cert/Cert_5_1_01_RouterAttach.py +++ b/tests/scripts/thread-cert/Cert_5_1_01_RouterAttach.py @@ -30,15 +30,20 @@ import time import unittest +import config +import mle import node LEADER = 1 ROUTER = 2 +SNIFFER = 3 + class Cert_5_1_01_RouterAttach(unittest.TestCase): + def setUp(self): self.nodes = {} - for i in range(1,3): + for i in range(1, 3): self.nodes[i] = node.Node(i) self.nodes[LEADER].set_panid(0xface) @@ -52,11 +57,16 @@ class Cert_5_1_01_RouterAttach(unittest.TestCase): self.nodes[ROUTER].enable_whitelist() self.nodes[ROUTER].set_router_selection_jitter(1) + self.sniffer = config.create_default_thread_sniffer(SNIFFER) + self.sniffer.start() + def tearDown(self): for node in list(self.nodes.values()): node.stop() del self.nodes + self.sniffer.stop() + def test(self): self.nodes[LEADER].start() self.nodes[LEADER].set_state('leader') @@ -66,5 +76,100 @@ class Cert_5_1_01_RouterAttach(unittest.TestCase): time.sleep(7) self.assertEqual(self.nodes[ROUTER].get_state(), 'router') + leader_messages = self.sniffer.get_messages_sent_by(LEADER) + router_messages = self.sniffer.get_messages_sent_by(ROUTER) + + # 1 - Leader + msg = leader_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + msg.assertSentWithHopLimit(255) + msg.assertSentToDestinationAddress("ff02::1") + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.Route64) + + # 2 - Router + msg = router_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + msg.assertSentWithHopLimit(255) + msg.assertSentToDestinationAddress("ff02::2") + msg.assertMleMessageContainsTlv(mle.Mode) + msg.assertMleMessageContainsTlv(mle.Challenge) + msg.assertMleMessageContainsTlv(mle.ScanMask) + msg.assertMleMessageContainsTlv(mle.Version) + + scan_mask_tlv = msg.get_mle_message_tlv(mle.ScanMask) + self.assertEqual(1, scan_mask_tlv.router) + self.assertEqual(0, scan_mask_tlv.end_device) + + # 3 - Leader + msg = leader_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + msg.assertSentToNode(self.nodes[ROUTER]) + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.LinkLayerFrameCounter) + msg.assertMleMessageContainsOptionalTlv(mle.MleFrameCounter) + msg.assertMleMessageContainsTlv(mle.Response) + msg.assertMleMessageContainsTlv(mle.Challenge) + msg.assertMleMessageContainsTlv(mle.LinkMargin) + msg.assertMleMessageContainsTlv(mle.Connectivity) + msg.assertMleMessageContainsTlv(mle.Version) + + # 4 - Router + msg = router_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + msg.assertSentToNode(self.nodes[LEADER]) + msg.assertMleMessageContainsTlv(mle.Response) + msg.assertMleMessageContainsTlv(mle.LinkLayerFrameCounter) + msg.assertMleMessageContainsOptionalTlv(mle.MleFrameCounter) + msg.assertMleMessageContainsTlv(mle.Mode) + msg.assertMleMessageContainsTlv(mle.Timeout) + msg.assertMleMessageContainsTlv(mle.Version) + msg.assertMleMessageContainsTlv(mle.TlvRequest) + msg.assertMleMessageDoesNotContainTlv(mle.AddressRegistration) + + # 5 - Leader + msg = leader_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE) + msg.assertSentToNode(self.nodes[ROUTER]) + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.Address16) + msg.assertMleMessageContainsOptionalTlv(mle.NetworkData) + msg.assertMleMessageContainsOptionalTlv(mle.Route64) + msg.assertMleMessageContainsOptionalTlv(mle.AddressRegistration) + + # 8 - Router + msg = router_messages.next_mle_message(mle.CommandType.LINK_REQUEST) + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.Challenge) + msg.assertMleMessageContainsTlv(mle.Version) + msg.assertMleMessageContainsTlv(mle.TlvRequest) + + tlv_request = msg.get_mle_message_tlv(mle.TlvRequest) + self.assertIn(mle.TlvType.LINK_MARGIN, tlv_request.tlvs) + + # 9 - Leader + msg = leader_messages.next_mle_message(mle.CommandType.LINK_ACCEPT_AND_REQUEST) + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.Response) + msg.assertMleMessageContainsTlv(mle.LinkLayerFrameCounter) + msg.assertMleMessageContainsTlv(mle.Version) + msg.assertMleMessageContainsTlv(mle.LinkMargin) + msg.assertMleMessageContainsOptionalTlv(mle.MleFrameCounter) + msg.assertMleMessageContainsOptionalTlv(mle.Challenge) + + # 10 - Router + msg = router_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + msg.assertSentWithHopLimit(255) + msg.assertSentToDestinationAddress("ff02::1") + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.Route64) + + # 11 - Leader, Router1 + for addr in self.nodes[LEADER].get_addrs(): + self.assertTrue(self.nodes[ROUTER].ping(addr)) + + + if __name__ == '__main__': unittest.main() diff --git a/tests/scripts/thread-cert/Cert_5_1_02_ChildAddressTimeout.py b/tests/scripts/thread-cert/Cert_5_1_02_ChildAddressTimeout.py index ca082cb2f..2409b8287 100755 --- a/tests/scripts/thread-cert/Cert_5_1_02_ChildAddressTimeout.py +++ b/tests/scripts/thread-cert/Cert_5_1_02_ChildAddressTimeout.py @@ -30,17 +30,22 @@ import time import unittest +import config +import mle import node LEADER = 1 ROUTER = 2 ED = 3 SED = 4 +SNIFFER = 5 + class Cert_5_1_02_ChildAddressTimeout(unittest.TestCase): + def setUp(self): self.nodes = {} - for i in range(1,5): + for i in range(1, 5): self.nodes[i] = node.Node(i) self.nodes[LEADER].set_panid(0xface) @@ -68,7 +73,13 @@ class Cert_5_1_02_ChildAddressTimeout(unittest.TestCase): self.nodes[SED].add_whitelist(self.nodes[ROUTER].get_addr64()) self.nodes[SED].enable_whitelist() + self.sniffer = config.create_default_thread_sniffer(SNIFFER) + self.sniffer.start() + def tearDown(self): + self.sniffer.stop() + del self.sniffer + for node in list(self.nodes.values()): node.stop() del self.nodes @@ -105,5 +116,29 @@ class Cert_5_1_02_ChildAddressTimeout(unittest.TestCase): if addr[0:4] != 'fe80': self.assertFalse(self.nodes[LEADER].ping(addr)) + leader_messages = self.sniffer.get_messages_sent_by(LEADER) + router1_messages = self.sniffer.get_messages_sent_by(ROUTER) + ed_messages = self.sniffer.get_messages_sent_by(ED) + sed_messages = self.sniffer.get_messages_sent_by(SED) + + # 1 - All + leader_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + router1_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + leader_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + router1_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + leader_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE) + + router1_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + ed_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + router1_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + ed_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + router1_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE) + + sed_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + router1_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + sed_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + router1_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE) + + if __name__ == '__main__': unittest.main() diff --git a/tests/scripts/thread-cert/Cert_5_1_03_RouterAddressReallocation.py b/tests/scripts/thread-cert/Cert_5_1_03_RouterAddressReallocation.py index ac066c590..e8495ee18 100755 --- a/tests/scripts/thread-cert/Cert_5_1_03_RouterAddressReallocation.py +++ b/tests/scripts/thread-cert/Cert_5_1_03_RouterAddressReallocation.py @@ -30,16 +30,21 @@ import time import unittest +import config +import mle import node LEADER = 1 ROUTER1 = 2 ROUTER2 = 3 +SNIFFER = 4 + class Cert_5_1_03_RouterAddressReallocation(unittest.TestCase): + def setUp(self): self.nodes = {} - for i in range(1,4): + for i in range(1, 4): self.nodes[i] = node.Node(i) self.nodes[LEADER].set_panid(0xface) @@ -62,7 +67,13 @@ class Cert_5_1_03_RouterAddressReallocation(unittest.TestCase): self.nodes[ROUTER2].enable_whitelist() self.nodes[ROUTER2].set_router_selection_jitter(1) + self.sniffer = config.create_default_thread_sniffer(SNIFFER) + self.sniffer.start() + def tearDown(self): + self.sniffer.stop() + del self.sniffer + for node in list(self.nodes.values()): node.stop() del self.nodes @@ -85,9 +96,68 @@ class Cert_5_1_03_RouterAddressReallocation(unittest.TestCase): self.nodes[ROUTER2].set_network_id_timeout(110) self.nodes[LEADER].stop() time.sleep(130) + self.assertEqual(self.nodes[ROUTER2].get_state(), 'leader') self.assertEqual(self.nodes[ROUTER1].get_state(), 'router') self.assertEqual(self.nodes[ROUTER1].get_addr16(), rloc16) + leader_messages = self.sniffer.get_messages_sent_by(LEADER) + router1_messages = self.sniffer.get_messages_sent_by(ROUTER1) + router2_messages = self.sniffer.get_messages_sent_by(ROUTER2) + + # 2 - All + leader_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + + router1_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + leader_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + router1_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + leader_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE) + + router2_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + leader_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + router1_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + router2_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + + # 5 - Router1 + # Router1 make two attempts to reconnect to its current Partition. + for _ in xrange(2): + msg = router1_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + msg.assertSentWithHopLimit(255) + msg.assertSentToDestinationAddress("ff02::2") + msg.assertMleMessageContainsTlv(mle.Mode) + msg.assertMleMessageContainsTlv(mle.Challenge) + msg.assertMleMessageContainsTlv(mle.ScanMask) + msg.assertMleMessageContainsTlv(mle.Version) + + scan_mask_tlv = msg.get_mle_message_tlv(mle.ScanMask) + self.assertEqual(1, scan_mask_tlv.router) + self.assertEqual(1, scan_mask_tlv.end_device) + + # 6 - Router1 + msg = router1_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + msg.assertSentWithHopLimit(255) + msg.assertSentToDestinationAddress("ff02::2") + msg.assertMleMessageContainsTlv(mle.Mode) + msg.assertMleMessageContainsTlv(mle.Challenge) + msg.assertMleMessageContainsTlv(mle.ScanMask) + msg.assertMleMessageContainsTlv(mle.Version) + + scan_mask_tlv = msg.get_mle_message_tlv(mle.ScanMask) + self.assertEqual(1, scan_mask_tlv.router) + self.assertEqual(0, scan_mask_tlv.end_device) + + # 7 - Router1 + msg = router1_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + msg.assertSentToNode(self.nodes[ROUTER2]) + msg.assertMleMessageContainsTlv(mle.Response) + msg.assertMleMessageContainsTlv(mle.LinkLayerFrameCounter) + msg.assertMleMessageContainsOptionalTlv(mle.MleFrameCounter) + msg.assertMleMessageContainsTlv(mle.Mode) + msg.assertMleMessageContainsTlv(mle.Timeout) + msg.assertMleMessageContainsTlv(mle.Version) + msg.assertMleMessageContainsTlv(mle.TlvRequest) + msg.assertMleMessageDoesNotContainTlv(mle.AddressRegistration) + + if __name__ == '__main__': unittest.main() diff --git a/tests/scripts/thread-cert/Cert_5_1_04_RouterAddressReallocation.py b/tests/scripts/thread-cert/Cert_5_1_04_RouterAddressReallocation.py index ced9a00e5..ef0b2956e 100755 --- a/tests/scripts/thread-cert/Cert_5_1_04_RouterAddressReallocation.py +++ b/tests/scripts/thread-cert/Cert_5_1_04_RouterAddressReallocation.py @@ -30,16 +30,21 @@ import time import unittest +import config +import mle import node LEADER = 1 ROUTER1 = 2 ROUTER2 = 3 +SNIFFER = 4 + class Cert_5_1_04_RouterAddressReallocation(unittest.TestCase): + def setUp(self): self.nodes = {} - for i in range(1,4): + for i in range(1, 4): self.nodes[i] = node.Node(i) self.nodes[LEADER].set_panid(0xface) @@ -62,7 +67,13 @@ class Cert_5_1_04_RouterAddressReallocation(unittest.TestCase): self.nodes[ROUTER2].enable_whitelist() self.nodes[ROUTER2].set_router_selection_jitter(1) + self.sniffer = config.create_default_thread_sniffer(SNIFFER) + self.sniffer.start() + def tearDown(self): + self.sniffer.stop() + del self.sniffer + for node in list(self.nodes.values()): node.stop() del self.nodes @@ -89,5 +100,77 @@ class Cert_5_1_04_RouterAddressReallocation(unittest.TestCase): self.assertEqual(self.nodes[ROUTER2].get_state(), 'router') self.assertEqual(self.nodes[ROUTER1].get_addr16(), rloc16) + leader_messages = self.sniffer.get_messages_sent_by(LEADER) + router1_messages = self.sniffer.get_messages_sent_by(ROUTER1) + router2_messages = self.sniffer.get_messages_sent_by(ROUTER2) + + # 2 - All + leader_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + + router1_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + leader_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + router1_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + leader_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE) + + router2_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + leader_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + router1_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + router2_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + + # 5 - Router1 + # Router1 make two attempts to reconnect to its current Partition. + for _ in xrange(2): + msg = router1_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + msg.assertSentWithHopLimit(255) + msg.assertSentToDestinationAddress("ff02::2") + msg.assertMleMessageContainsTlv(mle.Mode) + msg.assertMleMessageContainsTlv(mle.Challenge) + msg.assertMleMessageContainsTlv(mle.ScanMask) + msg.assertMleMessageContainsTlv(mle.Version) + + scan_mask_tlv = msg.get_mle_message_tlv(mle.ScanMask) + self.assertEqual(1, scan_mask_tlv.router) + self.assertEqual(1, scan_mask_tlv.end_device) + + # 6 - Router1 + msg = router1_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + msg.assertSentWithHopLimit(255) + msg.assertSentToDestinationAddress("ff02::2") + msg.assertMleMessageContainsTlv(mle.Mode) + msg.assertMleMessageContainsTlv(mle.Challenge) + msg.assertMleMessageContainsTlv(mle.ScanMask) + msg.assertMleMessageContainsTlv(mle.Version) + + scan_mask_tlv = msg.get_mle_message_tlv(mle.ScanMask) + self.assertEqual(1, scan_mask_tlv.router) + self.assertEqual(0, scan_mask_tlv.end_device) + + # 8 - Router2 + router2_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + router2_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + + # 9 - Router1 + msg = router1_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + msg.assertSentToNode(self.nodes[ROUTER2]) + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.LinkLayerFrameCounter) + msg.assertMleMessageContainsOptionalTlv(mle.MleFrameCounter) + msg.assertMleMessageContainsTlv(mle.Response) + msg.assertMleMessageContainsTlv(mle.Challenge) + msg.assertMleMessageContainsTlv(mle.LinkMargin) + msg.assertMleMessageContainsTlv(mle.Connectivity) + msg.assertMleMessageContainsTlv(mle.Version) + + msg = router1_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE) + msg.assertSentToNode(self.nodes[ROUTER2]) + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.Address16) + msg.assertMleMessageContainsOptionalTlv(mle.NetworkData) + msg.assertMleMessageContainsOptionalTlv(mle.Route64) + msg.assertMleMessageContainsOptionalTlv(mle.AddressRegistration) + + if __name__ == '__main__': unittest.main() diff --git a/tests/scripts/thread-cert/Cert_5_1_05_RouterAddressTimeout.py b/tests/scripts/thread-cert/Cert_5_1_05_RouterAddressTimeout.py index 7be88bae4..95e6381bd 100755 --- a/tests/scripts/thread-cert/Cert_5_1_05_RouterAddressTimeout.py +++ b/tests/scripts/thread-cert/Cert_5_1_05_RouterAddressTimeout.py @@ -30,15 +30,20 @@ import time import unittest +import config +import mle import node LEADER = 1 ROUTER1 = 2 +SNIFFER = 3 + class Cert_5_1_05_RouterAddressTimeout(unittest.TestCase): + def setUp(self): self.nodes = {} - for i in range(1,3): + for i in range(1, 3): self.nodes[i] = node.Node(i) self.nodes[LEADER].set_panid(0xface) @@ -52,7 +57,13 @@ class Cert_5_1_05_RouterAddressTimeout(unittest.TestCase): self.nodes[ROUTER1].enable_whitelist() self.nodes[ROUTER1].set_router_selection_jitter(1) + self.sniffer = config.create_default_thread_sniffer(SNIFFER) + self.sniffer.start() + def tearDown(self): + self.sniffer.stop() + del self.sniffer + for node in list(self.nodes.values()): node.stop() del self.nodes @@ -84,5 +95,35 @@ class Cert_5_1_05_RouterAddressTimeout(unittest.TestCase): self.assertEqual(self.nodes[ROUTER1].get_state(), 'router') self.assertEqual(self.nodes[ROUTER1].get_addr16(), rloc16) + leader_messages = self.sniffer.get_messages_sent_by(LEADER) + router1_messages = self.sniffer.get_messages_sent_by(ROUTER1) + + # 2 - All + leader_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + + router1_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + leader_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + + router1_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + leader_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE) + + # 3 - Router1 + router1_messages.next_mle_message(mle.CommandType.LINK_REQUEST) + router1_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + router1_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + + # 4 - Leader + leader_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + leader_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE) + + # 6 - Router1 + router1_messages.next_mle_message(mle.CommandType.LINK_REQUEST) + router1_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + router1_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + + # 7 - Leader + leader_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + leader_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE) + if __name__ == '__main__': unittest.main() diff --git a/tests/scripts/thread-cert/Cert_5_1_06_RemoveRouterId.py b/tests/scripts/thread-cert/Cert_5_1_06_RemoveRouterId.py index fe021689b..291adad67 100755 --- a/tests/scripts/thread-cert/Cert_5_1_06_RemoveRouterId.py +++ b/tests/scripts/thread-cert/Cert_5_1_06_RemoveRouterId.py @@ -30,15 +30,20 @@ import time import unittest +import config +import mle import node LEADER = 1 ROUTER1 = 2 +SNIFFER = 3 + class Cert_5_1_06_RemoveRouterId(unittest.TestCase): + def setUp(self): self.nodes = {} - for i in range(1,3): + for i in range(1, 3): self.nodes[i] = node.Node(i) self.nodes[LEADER].set_panid(0xface) @@ -52,7 +57,13 @@ class Cert_5_1_06_RemoveRouterId(unittest.TestCase): self.nodes[ROUTER1].enable_whitelist() self.nodes[ROUTER1].set_router_selection_jitter(1) + self.sniffer = config.create_default_thread_sniffer(SNIFFER) + self.sniffer.start() + def tearDown(self): + self.sniffer.stop() + del self.sniffer + for node in list(self.nodes.values()): node.stop() del self.nodes @@ -77,5 +88,26 @@ class Cert_5_1_06_RemoveRouterId(unittest.TestCase): for addr in self.nodes[ROUTER1].get_addrs(): self.assertTrue(self.nodes[LEADER].ping(addr)) + leader_messages = self.sniffer.get_messages_sent_by(LEADER) + router1_messages = self.sniffer.get_messages_sent_by(ROUTER1) + + # 1 - All + leader_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + + router1_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + leader_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + + router1_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + leader_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE) + + # 3 - Router1 + router1_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + leader_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + + router1_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + msg = leader_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE) + msg.assertSentToNode(self.nodes[ROUTER1]) + + if __name__ == '__main__': unittest.main() diff --git a/tests/scripts/thread-cert/Cert_5_1_08_RouterAttachConnectivity.py b/tests/scripts/thread-cert/Cert_5_1_08_RouterAttachConnectivity.py index f3b20d1e7..bdedf178e 100755 --- a/tests/scripts/thread-cert/Cert_5_1_08_RouterAttachConnectivity.py +++ b/tests/scripts/thread-cert/Cert_5_1_08_RouterAttachConnectivity.py @@ -30,6 +30,8 @@ import time import unittest +import config +import mle import node LEADER = 1 @@ -37,11 +39,14 @@ ROUTER1 = 2 ROUTER2 = 3 ROUTER3 = 4 ROUTER4 = 5 +SNIFFER = 6 + class Cert_5_1_08_RouterAttachConnectivity(unittest.TestCase): + def setUp(self): self.nodes = {} - for i in range(1,6): + for i in range(1, 6): self.nodes[i] = node.Node(i) self.nodes[LEADER].set_panid(0xface) @@ -80,7 +85,13 @@ class Cert_5_1_08_RouterAttachConnectivity(unittest.TestCase): self.nodes[ROUTER4].enable_whitelist() self.nodes[ROUTER4].set_router_selection_jitter(1) + self.sniffer = config.create_default_thread_sniffer(SNIFFER) + self.sniffer.start() + def tearDown(self): + self.sniffer.stop() + del self.sniffer + for node in list(self.nodes.values()): node.stop() del self.nodes @@ -95,5 +106,50 @@ class Cert_5_1_08_RouterAttachConnectivity(unittest.TestCase): time.sleep(5) self.assertEqual(self.nodes[i].get_state(), 'router') + leader_messages = self.sniffer.get_messages_sent_by(LEADER) + router1_messages = self.sniffer.get_messages_sent_by(ROUTER1) + router2_messages = self.sniffer.get_messages_sent_by(ROUTER2) + router3_messages = self.sniffer.get_messages_sent_by(ROUTER3) + router4_messages = self.sniffer.get_messages_sent_by(ROUTER4) + + # 1 - Leader, Router1, Router2, Router3 + leader_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + router1_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + router2_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + router3_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + + # 2 - Router4 + msg = router4_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + msg.assertSentWithHopLimit(255) + msg.assertSentToDestinationAddress("ff02::2") + msg.assertMleMessageContainsTlv(mle.Mode) + msg.assertMleMessageContainsTlv(mle.Challenge) + msg.assertMleMessageContainsTlv(mle.ScanMask) + msg.assertMleMessageContainsTlv(mle.Version) + + scan_mask_tlv = msg.get_mle_message_tlv(mle.ScanMask) + self.assertEqual(1, scan_mask_tlv.router) + self.assertEqual(0, scan_mask_tlv.end_device) + + # 3 - Router2, Router3 + msg = router2_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + msg.assertSentToNode(self.nodes[ROUTER4]) + + msg = router3_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + msg.assertSentToNode(self.nodes[ROUTER4]) + + # 4 - Router4 + msg = router4_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + msg.assertSentToNode(self.nodes[ROUTER3]) + msg.assertMleMessageContainsTlv(mle.Response) + msg.assertMleMessageContainsTlv(mle.LinkLayerFrameCounter) + msg.assertMleMessageContainsOptionalTlv(mle.MleFrameCounter) + msg.assertMleMessageContainsTlv(mle.Mode) + msg.assertMleMessageContainsTlv(mle.Timeout) + msg.assertMleMessageContainsTlv(mle.Version) + msg.assertMleMessageContainsTlv(mle.TlvRequest) + msg.assertMleMessageDoesNotContainTlv(mle.AddressRegistration) + + if __name__ == '__main__': unittest.main() diff --git a/tests/scripts/thread-cert/Cert_5_1_09_REEDAttachConnectivity.py b/tests/scripts/thread-cert/Cert_5_1_09_REEDAttachConnectivity.py index eb2ce7bea..5b44e8130 100755 --- a/tests/scripts/thread-cert/Cert_5_1_09_REEDAttachConnectivity.py +++ b/tests/scripts/thread-cert/Cert_5_1_09_REEDAttachConnectivity.py @@ -30,6 +30,8 @@ import time import unittest +import config +import mle import node LEADER = 1 @@ -37,11 +39,14 @@ ROUTER1 = 2 REED0 = 3 REED1 = 4 ROUTER2 = 5 +SNIFFER = 6 + class Cert_5_1_09_REEDAttachConnectivity(unittest.TestCase): + def setUp(self): self.nodes = {} - for i in range(1,6): + for i in range(1, 6): self.nodes[i] = node.Node(i) self.nodes[LEADER].set_panid(0xface) @@ -80,7 +85,13 @@ class Cert_5_1_09_REEDAttachConnectivity(unittest.TestCase): self.nodes[ROUTER2].enable_whitelist() self.nodes[ROUTER2].set_router_selection_jitter(1) + self.sniffer = config.create_default_thread_sniffer(SNIFFER) + self.sniffer.start() + def tearDown(self): + self.sniffer.stop() + del self.sniffer + for node in list(self.nodes.values()): node.stop() del self.nodes @@ -109,5 +120,63 @@ class Cert_5_1_09_REEDAttachConnectivity(unittest.TestCase): self.assertEqual(self.nodes[ROUTER2].get_state(), 'router') self.assertEqual(self.nodes[REED1].get_state(), 'router') + leader_messages = self.sniffer.get_messages_sent_by(LEADER) + router1_messages = self.sniffer.get_messages_sent_by(ROUTER1) + reed0_messages = self.sniffer.get_messages_sent_by(REED0) + reed1_messages = self.sniffer.get_messages_sent_by(REED1) + router2_messages = self.sniffer.get_messages_sent_by(ROUTER2) + + # 1 - Leader, Router1, REED1, REED2 + leader_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + router1_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + + # 2 - Router2 + msg = router2_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + msg.assertSentWithHopLimit(255) + msg.assertSentToDestinationAddress("ff02::2") + msg.assertMleMessageContainsTlv(mle.Mode) + msg.assertMleMessageContainsTlv(mle.Challenge) + msg.assertMleMessageContainsTlv(mle.ScanMask) + msg.assertMleMessageContainsTlv(mle.Version) + + scan_mask_tlv = msg.get_mle_message_tlv(mle.ScanMask) + self.assertEqual(1, scan_mask_tlv.router) + self.assertEqual(0, scan_mask_tlv.end_device) + + # 4 - Router2 + msg = router2_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + msg.assertSentWithHopLimit(255) + msg.assertSentToDestinationAddress("ff02::2") + msg.assertMleMessageContainsTlv(mle.Mode) + msg.assertMleMessageContainsTlv(mle.Challenge) + msg.assertMleMessageContainsTlv(mle.ScanMask) + msg.assertMleMessageContainsTlv(mle.Version) + + scan_mask_tlv = msg.get_mle_message_tlv(mle.ScanMask) + self.assertEqual(1, scan_mask_tlv.router) + self.assertEqual(1, scan_mask_tlv.end_device) + + # 5 - REED1, REED2 + msg = reed0_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + connectivity_tlv_reed0 = msg.get_mle_message_tlv(mle.Connectivity) + + msg = reed1_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + connectivity_tlv_reed1 = msg.get_mle_message_tlv(mle.Connectivity) + + self.assertGreater(connectivity_tlv_reed1.link_quality_3, connectivity_tlv_reed0.link_quality_3) + + # 6 - Router2 + msg = router2_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + msg.assertSentToNode(self.nodes[REED1]) + msg.assertMleMessageContainsTlv(mle.Response) + msg.assertMleMessageContainsTlv(mle.LinkLayerFrameCounter) + msg.assertMleMessageContainsOptionalTlv(mle.MleFrameCounter) + msg.assertMleMessageContainsTlv(mle.Mode) + msg.assertMleMessageContainsTlv(mle.Timeout) + msg.assertMleMessageContainsTlv(mle.Version) + msg.assertMleMessageContainsTlv(mle.TlvRequest) + msg.assertMleMessageDoesNotContainTlv(mle.AddressRegistration) + + if __name__ == '__main__': unittest.main() diff --git a/tests/scripts/thread-cert/Cert_5_1_10_RouterAttachLinkQuality.py b/tests/scripts/thread-cert/Cert_5_1_10_RouterAttachLinkQuality.py index 8ada535a1..97fae4179 100755 --- a/tests/scripts/thread-cert/Cert_5_1_10_RouterAttachLinkQuality.py +++ b/tests/scripts/thread-cert/Cert_5_1_10_RouterAttachLinkQuality.py @@ -30,17 +30,22 @@ import time import unittest +import config +import mle import node LEADER = 1 ROUTER1 = 2 ROUTER2 = 3 ROUTER3 = 4 +SNIFFER = 5 + class Cert_5_1_10_RouterAttachLinkQuality(unittest.TestCase): + def setUp(self): self.nodes = {} - for i in range(1,5): + for i in range(1, 5): self.nodes[i] = node.Node(i) self.nodes[LEADER].set_panid(0xface) @@ -70,7 +75,13 @@ class Cert_5_1_10_RouterAttachLinkQuality(unittest.TestCase): self.nodes[ROUTER3].enable_whitelist() self.nodes[ROUTER3].set_router_selection_jitter(1) + self.sniffer = config.create_default_thread_sniffer(SNIFFER) + self.sniffer.start() + def tearDown(self): + self.sniffer.stop() + del self.sniffer + for node in list(self.nodes.values()): node.stop() del self.nodes @@ -92,5 +103,44 @@ class Cert_5_1_10_RouterAttachLinkQuality(unittest.TestCase): time.sleep(5) self.assertEqual(self.nodes[ROUTER3].get_state(), 'router') + leader_messages = self.sniffer.get_messages_sent_by(LEADER) + router1_messages = self.sniffer.get_messages_sent_by(ROUTER1) + router2_messages = self.sniffer.get_messages_sent_by(ROUTER2) + router3_messages = self.sniffer.get_messages_sent_by(ROUTER3) + + # 1 - Leader, Router1, Router2 + leader_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + router1_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + router2_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + + # 3 - Router3 + msg = router3_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + msg.assertSentWithHopLimit(255) + msg.assertSentToDestinationAddress("ff02::2") + msg.assertMleMessageContainsTlv(mle.Mode) + msg.assertMleMessageContainsTlv(mle.Challenge) + msg.assertMleMessageContainsTlv(mle.ScanMask) + msg.assertMleMessageContainsTlv(mle.Version) + + # 4 - Router1, Router2 + msg = router1_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + msg.assertSentToNode(self.nodes[ROUTER3]) + + msg = router2_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + msg.assertSentToNode(self.nodes[ROUTER3]) + + # 5 - Router3 + msg = router3_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + msg.assertSentToNode(self.nodes[ROUTER1]) + msg.assertMleMessageContainsTlv(mle.Response) + msg.assertMleMessageContainsTlv(mle.LinkLayerFrameCounter) + msg.assertMleMessageContainsOptionalTlv(mle.MleFrameCounter) + msg.assertMleMessageContainsTlv(mle.Mode) + msg.assertMleMessageContainsTlv(mle.Timeout) + msg.assertMleMessageContainsTlv(mle.Version) + msg.assertMleMessageContainsTlv(mle.TlvRequest) + msg.assertMleMessageDoesNotContainTlv(mle.AddressRegistration) + + if __name__ == '__main__': unittest.main() diff --git a/tests/scripts/thread-cert/Cert_5_1_11_REEDAttachLinkQuality.py b/tests/scripts/thread-cert/Cert_5_1_11_REEDAttachLinkQuality.py index 2d85e27c5..a87edcb68 100755 --- a/tests/scripts/thread-cert/Cert_5_1_11_REEDAttachLinkQuality.py +++ b/tests/scripts/thread-cert/Cert_5_1_11_REEDAttachLinkQuality.py @@ -30,17 +30,22 @@ import time import unittest +import config +import mle import node LEADER = 1 REED = 2 ROUTER2 = 3 ROUTER1 = 4 +SNIFFER = 5 + class Cert_5_1_11_REEDAttachLinkQuality(unittest.TestCase): + def setUp(self): self.nodes = {} - for i in range(1,5): + for i in range(1, 5): self.nodes[i] = node.Node(i) self.nodes[LEADER].set_panid(0xface) @@ -70,7 +75,13 @@ class Cert_5_1_11_REEDAttachLinkQuality(unittest.TestCase): self.nodes[ROUTER1].enable_whitelist() self.nodes[ROUTER1].set_router_selection_jitter(1) + self.sniffer = config.create_default_thread_sniffer(SNIFFER) + self.sniffer.start() + def tearDown(self): + self.sniffer.stop() + del self.sniffer + for node in list(self.nodes.values()): node.stop() del self.nodes @@ -93,5 +104,46 @@ class Cert_5_1_11_REEDAttachLinkQuality(unittest.TestCase): self.assertEqual(self.nodes[ROUTER1].get_state(), 'router') self.assertEqual(self.nodes[REED].get_state(), 'router') + leader_messages = self.sniffer.get_messages_sent_by(LEADER) + router1_messages = self.sniffer.get_messages_sent_by(ROUTER1) + reed_messages = self.sniffer.get_messages_sent_by(REED) + router2_messages = self.sniffer.get_messages_sent_by(ROUTER2) + + # 1 - Leader. REED1, Router2 + leader_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + reed_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + router2_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + + # 3 - Router1 + msg = router1_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + msg.assertSentWithHopLimit(255) + msg.assertSentToDestinationAddress("ff02::2") + msg.assertMleMessageContainsTlv(mle.Mode) + msg.assertMleMessageContainsTlv(mle.Challenge) + msg.assertMleMessageContainsTlv(mle.ScanMask) + msg.assertMleMessageContainsTlv(mle.Version) + + scan_mask_tlv = msg.get_mle_message_tlv(mle.ScanMask) + self.assertEqual(1, scan_mask_tlv.router) + self.assertEqual(0, scan_mask_tlv.end_device) + + # 4 - Router2 + msg = router2_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + msg.assertSentToNode(self.nodes[ROUTER1]) + + # 5 - Router1 + msg = router1_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + msg.assertSentWithHopLimit(255) + msg.assertSentToDestinationAddress("ff02::2") + msg.assertMleMessageContainsTlv(mle.Mode) + msg.assertMleMessageContainsTlv(mle.Challenge) + msg.assertMleMessageContainsTlv(mle.ScanMask) + msg.assertMleMessageContainsTlv(mle.Version) + + scan_mask_tlv = msg.get_mle_message_tlv(mle.ScanMask) + self.assertEqual(1, scan_mask_tlv.router) + self.assertEqual(1, scan_mask_tlv.end_device) + + if __name__ == '__main__': unittest.main() diff --git a/tests/scripts/thread-cert/Cert_5_1_12_NewRouterNeighborSync.py b/tests/scripts/thread-cert/Cert_5_1_12_NewRouterNeighborSync.py index 36904752d..f05be878d 100755 --- a/tests/scripts/thread-cert/Cert_5_1_12_NewRouterNeighborSync.py +++ b/tests/scripts/thread-cert/Cert_5_1_12_NewRouterNeighborSync.py @@ -30,16 +30,21 @@ import time import unittest +import config +import mle import node LEADER = 1 ROUTER1 = 2 ROUTER2 = 3 +SNIFFER = 4 + class Cert_5_1_12_NewRouterSync(unittest.TestCase): + def setUp(self): self.nodes = {} - for i in range(1,4): + for i in range(1, 4): self.nodes[i] = node.Node(i) self.nodes[LEADER].set_panid(0xface) @@ -60,11 +65,52 @@ class Cert_5_1_12_NewRouterSync(unittest.TestCase): self.nodes[ROUTER2].enable_whitelist() self.nodes[ROUTER2].set_router_selection_jitter(1) + self.sniffer = config.create_default_thread_sniffer(SNIFFER) + self.sniffer.start() + def tearDown(self): + self.sniffer.stop() + del self.sniffer + for node in list(self.nodes.values()): node.stop() del self.nodes + def verify_step_4(self, router1_messages, router2_messages, req_receiver, accept_receiver): + if router2_messages.contains_mle_message(mle.CommandType.LINK_REQUEST) and \ + (router1_messages.contains_mle_message(mle.CommandType.LINK_ACCEPT) or + router1_messages.contains_mle_message(mle.CommandType.LINK_ACCEPT_AND_REQUEST)): + + msg = router2_messages.next_mle_message(mle.CommandType.LINK_REQUEST) + + msg.assertSentToNode(self.nodes[req_receiver]) + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.Challenge) + msg.assertMleMessageContainsTlv(mle.Version) + msg.assertMleMessageContainsTlv(mle.TlvRequest) + + msg = router1_messages.next_mle_message_of_one_of_command_types(mle.CommandType.LINK_ACCEPT_AND_REQUEST, + mle.CommandType.LINK_ACCEPT) + self.assertIsNotNone(msg) + + msg.assertSentToNode(self.nodes[accept_receiver]) + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.LinkLayerFrameCounter) + msg.assertMleMessageContainsOptionalTlv(mle.MleFrameCounter) + msg.assertMleMessageContainsTlv(mle.Version) + msg.assertMleMessageContainsTlv(mle.LinkMargin) + + if msg.mle.command.type == mle.CommandType.LINK_ACCEPT_AND_REQUEST: + msg.assertMleMessageContainsTlv(mle.TlvRequest) + msg.assertMleMessageContainsTlv(mle.Challenge) + + return True + + else: + return False + def test(self): self.nodes[LEADER].start() self.nodes[LEADER].set_state('leader') @@ -80,10 +126,30 @@ class Cert_5_1_12_NewRouterSync(unittest.TestCase): time.sleep(10) + leader_messages = self.sniffer.get_messages_sent_by(LEADER) + router1_messages = self.sniffer.get_messages_sent_by(ROUTER1) + router2_messages = self.sniffer.get_messages_sent_by(ROUTER2) + + # 2 - Router1 + msg = router1_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + msg.assertSentWithHopLimit(255) + msg.assertSentToDestinationAddress("ff02::1") + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.Route64) + self.nodes[ROUTER1].add_whitelist(self.nodes[ROUTER2].get_addr64()) self.nodes[ROUTER2].add_whitelist(self.nodes[ROUTER1].get_addr64()) - time.sleep(10) + time.sleep(35) + + leader_messages = self.sniffer.get_messages_sent_by(LEADER) + router1_messages = self.sniffer.get_messages_sent_by(ROUTER1) + router2_messages = self.sniffer.get_messages_sent_by(ROUTER2) + + # 4 - Router1, Router2 + self.assertTrue(self.verify_step_4(router1_messages, router2_messages, ROUTER1, ROUTER2) or + self.verify_step_4(router2_messages, router1_messages, ROUTER2, ROUTER1)) if __name__ == '__main__': unittest.main() diff --git a/tests/scripts/thread-cert/Cert_5_1_13_RouterReset.py b/tests/scripts/thread-cert/Cert_5_1_13_RouterReset.py index 75e41aa3b..57fc7bc13 100755 --- a/tests/scripts/thread-cert/Cert_5_1_13_RouterReset.py +++ b/tests/scripts/thread-cert/Cert_5_1_13_RouterReset.py @@ -30,15 +30,20 @@ import time import unittest +import config +import mle import node LEADER = 1 ROUTER = 2 +SNIFFER = 3 + class Cert_5_1_13_RouterReset(unittest.TestCase): + def setUp(self): self.nodes = {} - for i in range(1,3): + for i in range(1, 3): self.nodes[i] = node.Node(i) self.nodes[LEADER].set_panid(0xface) @@ -52,7 +57,13 @@ class Cert_5_1_13_RouterReset(unittest.TestCase): self.nodes[ROUTER].enable_whitelist() self.nodes[ROUTER].set_router_selection_jitter(1) + self.sniffer = config.create_default_thread_sniffer(SNIFFER) + self.sniffer.start() + def tearDown(self): + self.sniffer.stop() + del self.sniffer + for node in list(self.nodes.values()): node.stop() del self.nodes @@ -68,7 +79,7 @@ class Cert_5_1_13_RouterReset(unittest.TestCase): rloc16 = self.nodes[ROUTER].get_addr16() - self.nodes[ROUTER].stop(); + self.nodes[ROUTER].stop() time.sleep(5) self.nodes[ROUTER].start() @@ -76,5 +87,62 @@ class Cert_5_1_13_RouterReset(unittest.TestCase): self.assertEqual(self.nodes[ROUTER].get_state(), 'router') self.assertEqual(self.nodes[ROUTER].get_addr16(), rloc16) + leader_messages = self.sniffer.get_messages_sent_by(LEADER) + router1_messages = self.sniffer.get_messages_sent_by(ROUTER) + + # 1 - All + leader_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + + router1_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + leader_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + + router1_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + leader_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE) + + router1_messages.next_mle_message(mle.CommandType.LINK_REQUEST) + msg = leader_messages.next_mle_message_of_one_of_command_types(mle.CommandType.LINK_ACCEPT_AND_REQUEST, + mle.CommandType.LINK_ACCEPT) + self.assertIsNotNone(msg) + + # 2 - Router1 / Leader + msg = router1_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + msg.assertSentWithHopLimit(255) + msg.assertSentToDestinationAddress("ff02::1") + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.Route64) + + msg = leader_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + msg.assertSentWithHopLimit(255) + msg.assertSentToDestinationAddress("ff02::1") + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.Route64) + + # 4 - Router1 + msg = router1_messages.next_mle_message(mle.CommandType.LINK_REQUEST) + msg.assertSentToDestinationAddress("ff02::2") + msg.assertMleMessageContainsTlv(mle.Challenge) + msg.assertMleMessageContainsTlv(mle.Version) + msg.assertMleMessageContainsTlv(mle.TlvRequest) + + tlv_request = msg.get_mle_message_tlv(mle.TlvRequest) + self.assertIn(mle.TlvType.ROUTE64, tlv_request.tlvs) + self.assertIn(mle.TlvType.ADDRESS16, tlv_request.tlvs) + + # 5 - Leader + msg = leader_messages.next_mle_message(mle.CommandType.LINK_ACCEPT) + msg.assertSentToNode(self.nodes[ROUTER]) + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.Response) + msg.assertMleMessageContainsTlv(mle.LinkLayerFrameCounter) + msg.assertMleMessageContainsOptionalTlv(mle.MleFrameCounter) + msg.assertMleMessageContainsTlv(mle.Address16) + msg.assertMleMessageContainsTlv(mle.Version) + msg.assertMleMessageContainsTlv(mle.Route64) + msg.assertMleMessageContainsOptionalTlv(mle.Challenge) + + if __name__ == '__main__': unittest.main() diff --git a/tests/scripts/thread-cert/common.py b/tests/scripts/thread-cert/common.py new file mode 100644 index 000000000..baefaeefa --- /dev/null +++ b/tests/scripts/thread-cert/common.py @@ -0,0 +1,151 @@ +#!/usr/bin/python +# +# Copyright (c) 2016, 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 struct + +import ipaddress + +from binascii import hexlify + + +def enum(*sequential, **named): + enums = dict(zip(sequential, range(len(sequential))), **named) + names = dict((value, key) for key, value in enums.iteritems()) + enums['name'] = names + return type('Enum', (), enums) + + +class MessageInfo(object): + + def __init__(self): + self.aux_sec_hdr = None + self.aux_sec_hdr_bytes = None + + self.mhr_bytes = None + self.nonpayload_fields = None + + self.source_mac_address = None + self.destination_mac_address = None + + self._source_ipv6 = None + self._destination_ipv6 = None + + self.stable = None + self.payload_length = 0 + + def _convert_value_to_ip_address(self, value): + if isinstance(value, str): + value = unicode(value) + + elif isinstance(value, bytearray): + value = bytes(value) + + return ipaddress.ip_address(value) + + @property + def source_ipv6(self): + return self._source_ipv6 + + @source_ipv6.setter + def source_ipv6(self, value): + self._source_ipv6 = self._convert_value_to_ip_address(value) + + @property + def destination_ipv6(self): + return self._destination_ipv6 + + @destination_ipv6.setter + def destination_ipv6(self, value): + self._destination_ipv6 = self._convert_value_to_ip_address(value) + + +class MacAddress(object): + + SHORT = 0 + LONG = 1 + + def __init__(self, mac_address, _type, big_endian=True): + if _type == self.SHORT: + length = 2 + elif _type == self.LONG: + length = 8 + + if not big_endian: + mac_address = mac_address[::-1] + + self._mac_address = bytearray(mac_address[:length]) + self._type = _type + + @property + def type(self): + return self._type + + @property + def type_str(self): + return "SHORT" if self.type == self.SHORT else "LONG" + + @property + def mac_address(self): + return self._mac_address + + @property + def rloc(self): + return struct.unpack(">H", self._mac_address)[0] + + def convert_to_iid(self): + if self._type == self.SHORT: + return bytearray([0x00, 0x00, 0x00, 0xff, 0xfe, 0x00]) + self._mac_address[:2] + elif self._type == self.LONG: + return bytearray([self._mac_address[0] ^ 0x02]) + self._mac_address[1:] + else: + raise RuntimeError("Could not convert to IID. Invalid MAC address type: {}".format(self._type)) + + @classmethod + def from_eui64(cls, eui64, big_endian=True): + if not isinstance(eui64, bytearray): + raise RuntimeError("Could not create MAC address from EUI64. Invalid data type: {}".format(type(eui64))) + + return cls(eui64, MacAddress.LONG) + + @classmethod + def from_rloc16(cls, rloc16, big_endian=True): + if isinstance(rloc16, int) or isinstance(rloc16, long): + mac_address = struct.pack(">H", rloc16) + elif isinstance(rloc16, bytearray): + mac_address = rloc16[:2] + else: + raise RuntimeError("Could not create MAC address from RLOC16. Invalid data type: {}".format(type(rloc16))) + + return cls(mac_address, MacAddress.SHORT) + + def __eq__(self, other): + return (self.type == other.type) and (self.mac_address == other.mac_address) + + def __repr__(self): + return "MacAddress(mac_address=b'{}', type={})".format(hexlify(self.mac_address), self.type_str) diff --git a/tests/scripts/thread-cert/config.py b/tests/scripts/thread-cert/config.py new file mode 100644 index 000000000..df7895d9f --- /dev/null +++ b/tests/scripts/thread-cert/config.py @@ -0,0 +1,247 @@ +#!/usr/bin/python +# +# Copyright (c) 2016, 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 ipv6 +import lowpan +import message +import mle +import net_crypto +import network_data +import sniffer + + +DEFAULT_MASTER_KEY = bytearray([0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]) + + +def create_default_network_data_prefix_sub_tlvs_factories(): + return { + 0: network_data.HasRouteFactory( + routes_factory=network_data.RoutesFactory( + route_factory=network_data.RouteFactory()) + ), + 2: network_data.BorderRouterFactory(), + 3: network_data.LowpanIdFactory() + } + + +def create_default_network_data_prefix_sub_tlvs_factory(): + return network_data.PrefixSubTlvsFactory( + sub_tlvs_factories=create_default_network_data_prefix_sub_tlvs_factories()) + + +def create_default_network_data_service_sub_tlvs_factories(): + return { + 6: network_data.ServerFactory() + } + + +def create_default_network_data_service_sub_tlvs_factory(): + return network_data.ServiceSubTlvsFactory( + sub_tlvs_factories=create_default_network_data_service_sub_tlvs_factories()) + + +def create_default_network_data_tlvs_factories(): + return { + 1: network_data.PrefixFactory( + sub_tlvs_factory=create_default_network_data_prefix_sub_tlvs_factory() + ), + 5: network_data.ServiceFactory( + sub_tlvs_factory=create_default_network_data_service_sub_tlvs_factory() + ) + } + + +def create_default_network_data_tlvs_factory(): + return network_data.NetworkDataTlvsFactory( + sub_tlvs_factories=create_default_network_data_tlvs_factories()) + + +def create_default_mle_tlv_route64_factory(): + return mle.Route64Factory( + link_quality_and_route_data_factory=mle.LinkQualityAndRouteDataFactory()) + + +def create_default_mle_tlv_network_data_factory(): + return mle.NetworkDataFactory( + network_data_tlvs_factory=create_default_network_data_tlvs_factory()) + + +def create_default_mle_tlv_address_registration_factory(): + return mle.AddressRegistrationFactory( + addr_compressed_factory=mle.AddressCompressedFactory(), + addr_full_factory=mle.AddressFullFactory()) + + +def create_default_mle_tlvs_factories(): + return { + mle.TlvType.SOURCE_ADDRESS: mle.SourceAddressFactory(), + mle.TlvType.MODE: mle.ModeFactory(), + mle.TlvType.TIMEOUT: mle.TimeoutFactory(), + mle.TlvType.CHALLENGE: mle.ChallengeFactory(), + mle.TlvType.RESPONSE: mle.ResponseFactory(), + mle.TlvType.LINK_LAYER_FRAME_COUNTER: mle.LinkLayerFrameCounterFactory(), + mle.TlvType.MLE_FRAME_COUNTER: mle.MleFrameCounterFactory(), + mle.TlvType.ROUTE64: create_default_mle_tlv_route64_factory(), + mle.TlvType.ADDRESS16: mle.Address16Factory(), + mle.TlvType.LEADER_DATA: mle.LeaderDataFactory(), + mle.TlvType.NETWORK_DATA: create_default_mle_tlv_network_data_factory(), + mle.TlvType.TLV_REQUEST: mle.TlvRequestFactory(), + mle.TlvType.SCAN_MASK: mle.ScanMaskFactory(), + mle.TlvType.CONNECTIVITY: mle.ConnectivityFactory(), + mle.TlvType.LINK_MARGIN: mle.LinkMarginFactory(), + mle.TlvType.STATUS: mle.StatusFactory(), + mle.TlvType.VERSION: mle.VersionFactory(), + mle.TlvType.ADDRESS_REGISTRATION: create_default_mle_tlv_address_registration_factory(), + mle.TlvType.CHANNEL: mle.ChannelFactory(), + mle.TlvType.PANID: mle.PanIdFactory(), + mle.TlvType.ACTIVE_TIMESTAMP: mle.ActiveTimestampFactory(), + mle.TlvType.PENDING_TIMESTAMP: mle.PendingTimestampFactory(), + mle.TlvType.ACTIVE_OPERATIONAL_DATASET: mle.ActiveOperationalDatasetFactory(), + mle.TlvType.PENDING_OPERATIONAL_DATASET: mle.PendingOperationalDatasetFactory(), + mle.TlvType.THREAD_DISCOVERY: mle.ThreadDiscoveryFactory() + } + + +def create_default_mle_crypto_engine(master_key): + return net_crypto.CryptoEngine(crypto_material_creator=net_crypto.MleCryptoMaterialCreator(master_key)) + + +def create_default_mle_message_factory(master_key): + return mle.MleMessageFactory( + aux_sec_hdr_factory=net_crypto.AuxiliarySecurityHeaderFactory(), + mle_command_factory=mle.MleCommandFactory( + tlvs_factories=create_default_mle_tlvs_factories()), + crypto_engine=create_default_mle_crypto_engine(master_key)) + + +def create_default_ipv6_hop_by_hop_options_factories(): + return { + 109: ipv6.MPLOptionFactory() + } + + +def create_default_ipv6_hop_by_hop_options_factory(): + return ipv6.HopByHopOptionsFactory( + options_factories=create_default_ipv6_hop_by_hop_options_factories()) + + +def create_default_ipv6_udp_dst_port_factories(master_key): + mle_message_factory = create_default_mle_message_factory(master_key) + + return { + 19788: mle_message_factory, + + # TODO: Improve CoAP support + 61631: ipv6.UDPBytesPayloadFactory(), + 49152: ipv6.UDPBytesPayloadFactory(), + 49153: ipv6.UDPBytesPayloadFactory(), + 49154: ipv6.UDPBytesPayloadFactory() + } + + +def create_default_ipv6_icmp_body_factories(): + return { + 0: ipv6.ICMPv6DestinationUnreachableFactory(), + 128: ipv6.ICMPv6EchoBodyFactory(), + 129: ipv6.ICMPv6EchoBodyFactory() + } + + +def create_default_ipv6_upper_layer_factories(master_key): + return { + 17: ipv6.UDPDatagramFactory( + udp_header_factory=ipv6.UDPHeaderFactory(), + dst_port_factories=create_default_ipv6_udp_dst_port_factories(master_key) + ), + 58: ipv6.ICMPv6Factory( + body_factories=create_default_ipv6_icmp_body_factories() + ) + } + + +def create_default_lowpan_extension_headers_factories(): + return { + 0: lowpan.LowpanHopByHopFactory( + hop_by_hop_options_factory=create_default_ipv6_hop_by_hop_options_factory() + ) + } + + +def create_default_ipv6_extension_headers_factories(): + return { + 0: ipv6.HopByHopFactory( + hop_by_hop_options_factory=create_default_ipv6_hop_by_hop_options_factory()) + } + + +def create_default_ipv6_packet_factory(master_key): + return ipv6.IPv6PacketFactory( + ehf=create_default_ipv6_extension_headers_factories(), + ulpf=create_default_ipv6_upper_layer_factories(master_key) + ) + + +def create_default_lowpan_decompressor(context_manager): + return lowpan.LowpanDecompressor( + lowpan_ip_header_factory=lowpan.LowpanIpv6HeaderFactory( + context_manager=context_manager + ), + lowpan_extension_headers_factory=lowpan.LowpanExtensionHeadersFactory( + ext_headers_factories=create_default_lowpan_extension_headers_factories() + ), + lowpan_udp_header_factory=lowpan.LowpanUdpHeaderFactory() + ) + + +def create_default_thread_context_manager(): + context_manager = lowpan.ContextManager() + context_manager[0] = lowpan.Context("fd00:0db8::/64") + + return context_manager + + +def create_default_lowpan_parser(context_manager, master_key=DEFAULT_MASTER_KEY): + return lowpan.LowpanParser( + lowpan_mesh_header_factory=lowpan.LowpanMeshHeaderFactory(), + lowpan_decompressor=create_default_lowpan_decompressor(context_manager), + lowpan_fragements_buffers_manager=lowpan.LowpanFragmentsBuffersManager(), + ipv6_packet_factory=create_default_ipv6_packet_factory(master_key) + ) + + +def create_default_thread_message_factory(master_key=DEFAULT_MASTER_KEY): + context_manager = create_default_thread_context_manager() + lowpan_parser = create_default_lowpan_parser(context_manager, master_key) + + return message.MessageFactory(lowpan_parser=lowpan_parser) + + +def create_default_thread_sniffer(nodeid): + return sniffer.Sniffer(nodeid, create_default_thread_message_factory()) diff --git a/tests/scripts/thread-cert/ipv6.py b/tests/scripts/thread-cert/ipv6.py new file mode 100644 index 000000000..287c09777 --- /dev/null +++ b/tests/scripts/thread-cert/ipv6.py @@ -0,0 +1,1099 @@ +#!/usr/bin/python +# +# Copyright (c) 2016, 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 abc +import io +import struct + +from itertools import izip_longest +from ipaddress import ip_address + +# Next headers for IPv6 protocols +IPV6_NEXT_HEADER_HOP_BY_HOP = 0 +IPV6_NEXT_HEADER_TCP = 6 +IPV6_NEXT_HEADER_UDP = 17 +IPV6_NEXT_HEADER_ICMP = 58 + +UPPER_LAYER_PROTOCOLS = [ + IPV6_NEXT_HEADER_TCP, + IPV6_NEXT_HEADER_UDP, + IPV6_NEXT_HEADER_ICMP, +] + +# ICMP Protocol codes +ICMP_ECHO_REQUEST = 128 +ICMP_ECHO_RESPONSE = 129 + +# Default hop limit for IPv6 +HOP_LIMIT_DEFAULT = 64 + + +def calculate_checksum(data): + """ Calculate checksum from data bytes. + + How to calculate checksum (RFC 2460): + https://tools.ietf.org/html/rfc2460#page-27 + + Args: + data (bytes): input data from which checksum will be calculated + + Returns: + int: calculated checksum + """ + # Create halfwords from data bytes. Example: data[0] = 0x01, data[1] = 0xb2 => 0x01b2 + halfwords = [((byte0 << 8) | byte1) for byte0, byte1 in izip_longest(data[::2], data[1::2], fillvalue=0x00)] + + checksum = 0 + for halfword in halfwords: + checksum += halfword + checksum = (checksum & 0xFFFF) + (checksum >> 16) + + checksum ^= 0xFFFF + + if checksum == 0: + return 0xFFFF + else: + return checksum + + +class PacketFactory: + + """ Interface for classes that produce objects from data. """ + + def parse(self, data): + """ Convert data to object. + + Args: + data (bytes) + + """ + raise NotImplementedError + + +class BuildableFromBytes: + + """ Interface for classes which can be built from bytes. """ + + @classmethod + def from_bytes(cls, data): + """ Convert data to object. + + Args: + data (bytes) + + """ + raise NotImplementedError + + +class ConvertibleToBytes: + + """ Interface for classes which can be converted to bytes. """ + + def to_bytes(self): + """ Convert object to data. + + Returns: + bytes + """ + raise NotImplementedError + + def __len__(self): + """ Length of data (in bytes). + + Returns: + int + """ + raise NotImplementedError + + +class Header(object): + + """ Interface for header classes. """ + + __metaclass__ = abc.ABCMeta + + @abc.abstractproperty + def type(self): + """ Number which can be used in the next header field in IPv6 header or next headers. + + Returns: + int + """ + + +class ExtensionHeader(object): + + """ Base for classes representing Extension Headers in IPv6 packets. """ + + def __init__(self, next_header, hdr_ext_len=0): + self.next_header = next_header + self.hdr_ext_len = hdr_ext_len + + +class UpperLayerProtocol(Header, ConvertibleToBytes): + + """ Base for classes representing upper layer protocol payload in IPv6 packets. """ + + def __init__(self, header): + self.header = header + + @property + def checksum(self): + """ Return checksum from upper layer protocol header. """ + return self.header.checksum + + @checksum.setter + def checksum(self, value): + """ Set checksum value in upper layer protocol header. """ + self.header.checksum = value + + def is_valid_checksum(self): + """ Return information if set checksum is valid. + + It is not possible to get zero from checksum calculation. + Zero indicates invalid checksum value. + + Returns: + bool + """ + return self.checksum != 0 + + +class IPv6PseudoHeader(ConvertibleToBytes): + + """ Class representing IPv6 pseudo header which is required to calculate + upper layer protocol (like e.g. UDP or ICMPv6) checksum. + + This class is used only during upper layer protocol checksum calculation. Do not use it outside of this module. + + """ + + def __init__(self, source_address, destination_address, payload_length, next_header): + self._source_address = self._convert_to_ipaddress(source_address) + self._destination_address = self._convert_to_ipaddress(destination_address) + self.payload_length = payload_length + self.next_header = next_header + + def _convert_to_ipaddress(self, value): + if isinstance(value, bytearray): + value = bytes(value) + + elif isinstance(value, unicode): + value = str(value) + + return ip_address(value) + + @property + def source_address(self): + return self._source_address + + @source_address.setter + def source_address(self, value): + self._source_address = self._convert_to_ipaddress(value) + + @property + def destination_address(self): + return self._destination_address + + @destination_address.setter + def destination_address(self, value): + self._source_address = self._convert_to_ipaddress(value) + + def to_bytes(self): + data = bytearray() + data += self.source_address.packed + data += self.destination_address.packed + data += struct.pack(">I", self.payload_length) + data += struct.pack(">I", self.next_header) + + return data + + +class IPv6Header(object, ConvertibleToBytes, BuildableFromBytes): + + """ Class representing IPv6 packet header. """ + + _version = 6 + + _header_length = 40 + + def __init__(self, source_address, destination_address, traffic_class=0, flow_label=0, hop_limit=64, + payload_length=0, next_header=0): + self.version = self._version + self._source_address = self._convert_to_ipaddress(source_address) + self._destination_address = self._convert_to_ipaddress(destination_address) + self.traffic_class = traffic_class + self.flow_label = flow_label + self.hop_limit = hop_limit + self.payload_length = payload_length + self.next_header = next_header + + def _convert_to_ipaddress(self, value): + if isinstance(value, bytearray): + value = bytes(value) + + elif isinstance(value, str): + value = unicode(value) + + return ip_address(value) + + @property + def source_address(self): + return self._source_address + + @source_address.setter + def source_address(self, value): + self._source_address = self._convert_to_ipaddress(value) + + @property + def destination_address(self): + return self._destination_address + + def to_bytes(self): + data = bytearray([ + ((self.version & 0x0F) << 4) | ((self.traffic_class >> 4) & 0x0F), + ((self.traffic_class & 0x0F) << 4) | ((self.flow_label >> 16) & 0x0F), + ((self.flow_label >> 8) & 0xFF), + ((self.flow_label & 0xFF)) + ]) + data += struct.pack(">H", self.payload_length) + data += bytearray([self.next_header, self.hop_limit]) + data += self.source_address.packed + data += self.destination_address.packed + + return data + + @classmethod + def from_bytes(cls, data): + b = bytearray(data.read(4)) + + version = (b[0] >> 4) & 0x0F + traffic_class = ((b[0] & 0x0F) << 4) | ((b[1] >> 4) & 0x0F) + flow_label = ((b[1] & 0x0F) << 16) | (b[2] << 8) | b[3] + + payload_length = struct.unpack(">H", data.read(2))[0] + next_header = ord(data.read(1)) + hop_limit = ord(data.read(1)) + src_addr = bytearray(data.read(16)) + dst_addr = bytearray(data.read(16)) + + return cls(src_addr, + dst_addr, + traffic_class, + flow_label, + hop_limit, + payload_length, + next_header) + + def __repr__(self): + return "IPv6Header(source_address={}, destination_address={}, next_header={}, payload_length={}, \ + hop_limit={}, traffic_class={}, flow_label={})".format(self.source_address.compressed, + self.destination_address.compressed, + self.next_header, + self.payload_length, + self.hop_limit, + self.traffic_class, + self.flow_label) + + def __len__(self): + return self._header_length + + +class IPv6Packet(ConvertibleToBytes): + + """ Class representing IPv6 packet. + + IPv6 packet consists of IPv6 header, optional extension header, and upper layer protocol. + + IPv6 packet + + +-------------+----------------------------------+----------------------------------------------+ + | | | | + | IPv6 header | extension headers (zero or more) | upper layer protocol (e.g. UDP, TCP, ICMPv6) | + | | | | + +-------------+----------------------------------+----------------------------------------------+ + + Extension headers: + - HopByHop + - Routing header (not implemented in this module) + + Upper layer protocols: + - ICMPv6 + - UDP + - TCP (not implemented in this module) + + Example: + IPv6 packet construction without extension headers: + + ipv6_packet = IPv6Packet(IPv6Header("fd00:1234:4555::ff:fe00:1800", "ff03::1"), + ICMPv6(ICMPv6Header(128, 0), + ICMPv6EchoBody(0, 2, bytes([0x80, 0x00, 0xc7, 0xbf, 0x00, 0x00, 0x00, 0x01, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41])))) + + IPv6 packet construction with extension headers: + + ipv6_packet = IPv6Packet(IPv6Header("fd00:1234:4555::ff:fe00:1800", "ff03::1"), + ICMPv6(ICMPv6Header(128, 0), + ICMPv6EchoBody(0, 2, bytes([0x80, 0x00, 0xc7, 0xbf, 0x00, 0x00, 0x00, 0x01, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41])), + [HopByHop(options=[ + HopByHopOption(HopByHopOptionHeader(_type=0x6d), + MPLOption(S=1, M=0, V=0, sequence=2, seed_id=bytes([0x00, 0x18]))) + ])]) + + """ + + def __init__(self, ipv6_header, upper_layer_protocol, extension_headers=None): + self.ipv6_header = ipv6_header + + self.upper_layer_protocol = upper_layer_protocol + + self.extension_headers = extension_headers if extension_headers is not None else [] + + self._update_next_header_values_in_headers() + + if not upper_layer_protocol.is_valid_checksum(): + self.upper_layer_protocol.checksum = self.calculate_checksum() + + def _validate_checksum(self): + checksum = self.calculate_checksum() + + if self.upper_layer_protocol.checksum != checksum: + raise RuntimeError("Could not create IPv6 packet. " + "Invalid checksum: {}!={}".format(self.upper_layer_protocol.checksum, checksum)) + + self.upper_layer_protocol.checksum = checksum + + def _update_payload_length_value_in_ipv6_header(self): + self.ipv6_header.payload_length = len(self.upper_layer_protocol) + \ + sum([len(extension_header) for extension_header in self.extension_headers]) + + def _update_next_header_values_in_headers(self): + last_header = self.ipv6_header + + for extension_header in self.extension_headers: + last_header.next_header = extension_header.type + last_header = extension_header + + last_header.next_header = self.upper_layer_protocol.type + + def calculate_checksum(self): + saved_checksum = self.upper_layer_protocol.checksum + + self.upper_layer_protocol.checksum = 0 + + upper_layer_protocol_bytes = self.upper_layer_protocol.to_bytes() + + self.upper_layer_protocol.checksum = saved_checksum + + pseudo_header = IPv6PseudoHeader(self.ipv6_header.source_address, + self.ipv6_header.destination_address, + len(upper_layer_protocol_bytes), + self.upper_layer_protocol.type) + + return calculate_checksum(pseudo_header.to_bytes() + upper_layer_protocol_bytes) + + def to_bytes(self): + self._update_payload_length_value_in_ipv6_header() + self._update_next_header_values_in_headers() + self.upper_layer_protocol.checksum = self.calculate_checksum() + + ipv6_packet = self.ipv6_header.to_bytes() + + for extension_header in self.extension_headers: + ipv6_packet += extension_header.to_bytes() + + ipv6_packet += self.upper_layer_protocol.to_bytes() + + return ipv6_packet + + def __repr__(self): + return "IPv6Packet(\n\theader={})".format(self.ipv6_header) + + +class UDPHeader(object, ConvertibleToBytes, BuildableFromBytes): + + """ Class representing UDP datagram header. + + This header is required to construct UDP datagram. + + """ + + _header_length = 8 + + def __init__(self, src_port, dst_port, payload_length=0, checksum=0): + self.src_port = src_port + self.dst_port = dst_port + + self._payload_length = payload_length + self.checksum = checksum + + @property + def type(self): + return 17 + + @property + def payload_length(self): + return self._payload_length + + @payload_length.setter + def payload_length(self, value): + self._payload_length = self._header_length + value + + def to_bytes(self): + data = struct.pack(">H", self.src_port) + data += struct.pack(">H", self.dst_port) + data += struct.pack(">H", self.payload_length) + data += struct.pack(">H", self.checksum) + + return data + + @classmethod + def from_bytes(cls, data): + src_port = struct.unpack(">H", data.read(2))[0] + dst_port = struct.unpack(">H", data.read(2))[0] + payload_length = struct.unpack(">H", data.read(2))[0] + checksum = struct.unpack(">H", data.read(2))[0] + + return cls(src_port, dst_port, payload_length, checksum) + + def __len__(self): + return self._header_length + + +class UDPDatagram(UpperLayerProtocol): + + """ Class representing UDP datagram. + + UDP is an upper layer protocol for IPv6 so it can be passed to IPv6 packet as upper_layer_protocol. + + This class consists of a UDP header and payload. The example below shows how a UDP datagram can be constructed. + + Example: + udp_dgram = UDPDatagram(UDPHeader(src_port=19788, dst_port=19788), + bytes([0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x09, 0x01, 0x01, 0x0b, 0x03, + 0x04, 0xc6, 0x69, 0x73, 0x51, 0x0e, 0x01, 0x80, + 0x12, 0x02, 0x00, 0x01, 0xde, 0xad, 0xbe, 0xef])) + + """ + + @property + def type(self): + return 17 + + def __init__(self, header, payload): + super(UDPDatagram, self).__init__(header) + self.payload = payload + + def to_bytes(self): + self.header.payload_length = len(self.payload) + + data = bytearray() + data += self.header.to_bytes() + data += self.payload.to_bytes() + return data + + def __len__(self): + return len(self.header) + len(self.payload) + + +class ICMPv6Header(ConvertibleToBytes, BuildableFromBytes): + + """ Class representing ICMPv6 message header. + + This header is required to construct ICMPv6 message. + + """ + + _header_length = 4 + + def __init__(self, _type, code, checksum=0): + self.type = _type + self.code = code + + self.checksum = checksum + + def to_bytes(self): + return bytearray([self.type, self.code]) + struct.pack(">H", self.checksum) + + @classmethod + def from_bytes(cls, data): + _type = ord(data.read(1)) + code = ord(data.read(1)) + checksum = struct.unpack(">H", data.read(2))[0] + + return cls(_type, code, checksum) + + def __len__(self): + return self._header_length + + +class ICMPv6(UpperLayerProtocol): + + """ Class representing ICMPv6 message. + + ICMPv6 is an upper layer protocol for IPv6 so it can be passed to IPv6 packet as upper_layer_protocol. + + This class consists of an ICMPv6 header and body. The example below shows how an ICMPv6 message can be constructed. + + Example: + icmpv6_msg = ICMPv6(ICMPv6Header(128, 0), + ICMPv6EchoBody(0, 2, bytes([0x80, 0x00, 0xc7, 0xbf, 0x00, 0x00, 0x00, 0x01, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41]))) + + """ + @property + def type(self): + return 58 + + def __init__(self, header, body): + super(ICMPv6, self).__init__(header) + self.body = body + + def to_bytes(self): + return bytearray(self.header.to_bytes() + self.body.to_bytes()) + + def __len__(self): + return len(self.header) + len(self.body) + + +class HopByHop(ExtensionHeader): + + """ Class representing HopByHop extension header. + + HopByHop extension header consists of: + - next_header type + - extension header length which is multiple of 8 + - options + + """ + _one_byte_padding = 0x00 + _many_bytes_padding = 0x01 + + @property + def type(self): + return 0 + + def __init__(self, next_header=None, options=None, hdr_ext_len=None): + super(HopByHop, self).__init__(next_header, hdr_ext_len) + self.options = options if options is not None else [] + + if hdr_ext_len is not None: + self.hdr_ext_len = hdr_ext_len + else: + payload_length = self._calculate_payload_length() + self.hdr_ext_len = self._calculate_hdr_ext_len(payload_length) + + def _calculate_payload_length(self): + payload_length = 2 + + for option in self.options: + payload_length += len(option) + + return payload_length + + def _calculate_hdr_ext_len(self, payload_length): + count = payload_length >> 3 + + if (payload_length & 0x7) == 0 and count > 0: + return count - 1 + + return count + + def to_bytes(self): + data = bytearray([self.next_header, self.hdr_ext_len]) + + for option in self.options: + data += option.to_bytes() + + # Padding + # + # More details: + # https://tools.ietf.org/html/rfc2460#section-4.2 + # + excess_bytes = len(data) & 0x7 + + if excess_bytes > 0: + padding_length = 8 - excess_bytes + + if padding_length == 1: + data += bytearray([self._one_byte_padding]) + + else: + padding_length -= 2 + data += bytearray([self._many_bytes_padding, padding_length]) + data += bytearray([0x00 for _ in range(padding_length)]) + + return data + + def __len__(self): + """ HopByHop extension header length + + More details: + https://tools.ietf.org/html/rfc2460#section-4.3 + + """ + return (self.hdr_ext_len + 1) * 8 + + +class HopByHopOptionHeader(ConvertibleToBytes, BuildableFromBytes): + + """ Class representing HopByHop option header. """ + + _header_length = 2 + + def __init__(self, _type, length=None): + self.type = _type + self.length = length if length is not None else 0 + + def to_bytes(self): + return bytearray([self.type, self.length]) + + @classmethod + def from_bytes(cls, data): + _type = ord(data.read(1)) + length = ord(data.read(1)) + return cls(_type, length) + + def __len__(self): + return self._header_length + + +class HopByHopOption(ConvertibleToBytes): + + """ Class representing HopByHop option. + + Class consists of two elements: HopByHopOptionHeader and value (e.g. for MPLOption). + + The following example shows how any HopByHop option can be constructed. + + Example: + HopByHop(next_header=0x3a, + options=[HopByHopOption(HopByHopOptionHeader(_type=0x6d), + MPLOption(S=1, M=0, V=0, sequence=2, seed_id=bytes([0x00, 0x18]))) + + """ + + def __init__(self, header, value): + self.value = value + + self.header = header + self.header.length = len(self.value) + + def to_bytes(self): + return self.header.to_bytes() + self.value.to_bytes() + + def __len__(self): + return len(self.header) + len(self.value) + + +class MPLOption(ConvertibleToBytes): + + """ Class representing MPL option. """ + + _header_length = 2 + + _seed_id_length = { + 0: 0, + 1: 2, + 2: 8, + 3: 16 + } + + def __init__(self, S, M, V, sequence, seed_id): + self.S = S + self.M = M + self.V = V + self.sequence = sequence + self.seed_id = seed_id + + def to_bytes(self): + smv = ((self.S & 0x03) << 6) | ((self.M & 0x01) << 5) | ((self.V & 0x01) << 4) + + return bytearray([smv, self.sequence]) + self.seed_id + + @classmethod + def from_bytes(cls, data): + b = ord(data.read(1)) + + s = ((b >> 6) & 0x03) + m = ((b >> 5) & 0x01) + v = ((b >> 4) & 0x01) + + sequence = ord(data.read(1)) + seed_id = data.read(cls._seed_id_length[s]) + + return cls(s, m, v, sequence, seed_id) + + def __len__(self): + return self._header_length + self._seed_id_length[self.S] + + +class IPv6PacketFactory(PacketFactory): + + """ Factory that produces IPv6 packets from data. + + This factory must be initialized with factories which allow to parse extension headers and upper layer protocols. + + The following example shows preferable setup of IPv6PacketFactory. + + Header types: + 0: HopByHop + 17: UDP + 58: ICMPv6 + + Option types: + 109: MPL + + ICMPv6 body types: + 128: Echo request + 129: Echo response + + Example usage: + + ipv6_factory = IPv6PacketFactory( + ehf={ + 0: HopByHopFactory(options_factories={ + 109: MPLOptionFactory() + }) + }, + ulpf={ + 17: UDPDatagramFactory(dst_port_factories={ + 19788: MLEMessageFactory(), + 19789: CoAPMessageFactory() + }), + 58: ICMPv6Factory(body_factories={ + 128: ICMPv6EchoBodyFactory(), + 129: ICMPv6EchoBodyFactory() + }) + } + ) + + """ + + def __init__(self, ehf=None, ulpf=None): + """ + ehf - Extension Header Factory + ulpf - Upper Layer Protocol Factory + + Args: + ehf(dict[int: PacketFactory]): Dictionary mapping extension header types on specialized factories. + ulpf(dict[int: PacketFactory]): Dictionary mapping upper layer protocol types on specialized factories. + """ + self._ehf = ehf if ehf is not None else {} + self._ulpf = ulpf if ulpf is not None else {} + + def _is_extension_header(self, header_type): + return not header_type in UPPER_LAYER_PROTOCOLS + + def _get_extension_header_factory_for(self, next_header): + try: + return self._ehf[next_header] + except KeyError: + raise RuntimeError("Could not get Extension Header factory for next_header={}.".format(next_header)) + + def _get_upper_layer_protocol_factory_for(self, next_header): + try: + return self._ulpf[next_header] + except KeyError: + raise RuntimeError("Could not get Upper Layer Protocol factory for next_header={}.".format(next_header)) + + def _parse_extension_headers(self, data, next_header, message_info): + extension_headers = [] + + while self._is_extension_header(next_header): + factory = self._get_extension_header_factory_for(next_header) + + extension_header = factory.parse(data, message_info) + + next_header = extension_header.next_header + + extension_headers.append(extension_header) + + return next_header, extension_headers + + def _parse_upper_layer_protocol(self, data, next_header, message_info): + factory = self._get_upper_layer_protocol_factory_for(next_header) + + return factory.parse(data, message_info) + + def parse(self, data, message_info): + ipv6_header = IPv6Header.from_bytes(data) + + message_info.source_ipv6 = ipv6_header.source_address + message_info.destination_ipv6 = ipv6_header.destination_address + + next_header, extension_headers = self._parse_extension_headers(data, ipv6_header.next_header, message_info) + + upper_layer_protocol = self._parse_upper_layer_protocol(data, next_header, message_info) + + return IPv6Packet(ipv6_header, upper_layer_protocol, extension_headers) + + +class HopByHopOptionsFactory: + + """ Factory that produces HopByHop options. """ + + _one_byte_padding = 0x00 + _many_bytes_padding = 0x01 + + def __init__(self, options_factories=None): + self._options_factories = options_factories if options_factories is not None else {} + + def _get_HopByHopOption_value_factory(self, _type): + try: + return self._options_factories[_type] + except KeyError: + raise RuntimeError("Could not find HopByHopOption value factory for type={}.".format(_type)) + + def parse(self, data, message_info): + options = [] + + while data.tell() < len(data.getvalue()): + option_header = HopByHopOptionHeader.from_bytes(data) + + if option_header.type == self._one_byte_padding: + # skip one byte padding + data.read(1) + + elif option_header.type == self._many_bytes_padding: + # skip n bytes padding + data.read(option_header.length) + + else: + factory = self._get_HopByHopOption_value_factory(option_header.type) + + option_data = data.read(option_header.length) + + option = HopByHopOption(option_header, factory.parse(io.BytesIO(option_data), message_info)) + + options.append(option) + + return options + + +class HopByHopFactory(PacketFactory): + + """ Factory that produces HopByHop extension headers from data. """ + + def __init__(self, hop_by_hop_options_factory): + self._hop_by_hop_options_factory = hop_by_hop_options_factory + + def _calculate_extension_header_length(self, hdr_ext_len): + return (hdr_ext_len + 1) * 8 + + def parse(self, data, message_info): + next_header = ord(data.read(1)) + + hdr_ext_len = ord(data.read(1)) + + # Note! Two bytes were read (next_header and hdr_ext_len) so they must be substracted from header length + hop_by_hop_length = self._calculate_extension_header_length(hdr_ext_len) - 2 + + hop_by_hop_data = data.read(hop_by_hop_length) + + options = self._hop_by_hop_options_factory.parse(io.BytesIO(hop_by_hop_data), message_info) + + hop_by_hop = HopByHop(next_header, options, hdr_ext_len) + + message_info.payload_length += len(hop_by_hop) + + return hop_by_hop + + +class MPLOptionFactory(PacketFactory): + + """ Factory that produces MPL options for HopByHop extension header. """ + + def parse(self, data, message_info): + return MPLOption.from_bytes(data) + + +class UDPHeaderFactory: + + """ Factory that produces UDP header. """ + + def parse(self, data, message_info): + return UDPHeader.from_bytes(data) + + +class UDPDatagramFactory(PacketFactory): + + """ Factory that produces UDP datagrams. """ + + def __init__(self, udp_header_factory, dst_port_factories=None): + """ + Args: + dst_port_factories (PacketFactory): Factories parse UDP payload based on destination port. + """ + self._udp_header_factory = udp_header_factory + self._dst_port_factories = dst_port_factories if dst_port_factories is not None else {} + + def _get_payload_factory(self, dst_port): + try: + return self._dst_port_factories[dst_port] + + except KeyError: + raise RuntimeError( + "Could not find factory to parse UDP datagram payload based on destination port: {}".format(dst_port)) + + def parse(self, data, message_info): + udp_header = self._udp_header_factory.parse(data, message_info) + + factory = self._get_payload_factory(udp_header.dst_port) + + message_info.payload_length += len(udp_header) + (len(data.getvalue()) - data.tell()) + + return UDPDatagram(udp_header, factory.parse(data, message_info)) + + +class ICMPv6Factory(PacketFactory): + + """ Factory that produces ICMPv6 messages from data. """ + + def __init__(self, body_factories=None): + self._body_factories = body_factories if body_factories is not None else {} + + def _get_icmpv6_body_factory(self, _type): + try: + return self._body_factories[_type] + + except KeyError: + raise RuntimeError("Could not find factory to parse ICMP body. Unsupported ICMP type: {}".format(_type)) + + def parse(self, data, message_info): + header = ICMPv6Header.from_bytes(data) + + factory = self._get_icmpv6_body_factory(header.type) + + message_info.payload_length += len(header) + (len(data.getvalue()) - data.tell()) + + return ICMPv6(header, factory.parse(data, message_info)) + + +class ICMPv6EchoBodyFactory(PacketFactory): + + """ Factory that produces ICMPv6 echo message body. """ + + def parse(self, data, message_info): + return ICMPv6EchoBody.from_bytes(data) + + +class UDPBytesPayload(ConvertibleToBytes, BuildableFromBytes): + + """ Class representing payload of UDP datagram. """ + + def __init__(self, data): + self.data = data + + def to_bytes(self): + return bytearray(self.data) + + @classmethod + def from_bytes(cls, data): + return cls(data) + + def __len__(self): + return len(self.data) + + +class UDPBytesPayloadFactory(PacketFactory): + + """ Factory that produces payload of UDP datagram. """ + + def parse(self, data, message_info): + return UDPBytesPayload(data.read()) + + +class ICMPv6EchoBody(ConvertibleToBytes, BuildableFromBytes): + + """ Class representing body of ICMPv6 echo messages. """ + + _header_length = 4 + + def __init__(self, identifier, sequence_number, data): + self.identifier = identifier + self.sequence_number = sequence_number + self.data = data + + def to_bytes(self): + data = struct.pack(">H", self.identifier) + data += struct.pack(">H", self.sequence_number) + data += self.data + return data + + @classmethod + def from_bytes(cls, data): + identifier = struct.unpack(">H", data.read(2))[0] + sequence_number = struct.unpack(">H", data.read(2))[0] + + return cls(identifier, sequence_number, data.read()) + + def __len__(self): + return self._header_length + len(self.data) + + +class ICMPv6DestinationUnreachableFactory(PacketFactory): + + """ Factory that produces ICMPv6 echo message body. """ + + def parse(self, data, message_info): + return ICMPv6DestinationUnreachable.from_bytes(data) + + +class ICMPv6DestinationUnreachable(ConvertibleToBytes, BuildableFromBytes): + + """ Class representing body of ICMPv6 Destination Unreachable messages. """ + + _header_length = 4 + _unused = 0 + + def __init__(self, data): + self.data = data + + def to_bytes(self): + data = bytearray(struct.pack(">I", self._unused)) + data += self.data + return data + + @classmethod + def from_bytes(cls, data): + unused = struct.unpack(">I", data.read(4))[0] + if unused != 0: + raise RuntimeError( + "Invalid value of unused field in the ICMPv6 Destination Unreachable data. Expected value: 0.") + + return cls(bytearray(data.read())) + + def __len__(self): + return self._header_length + len(self.data) diff --git a/tests/scripts/thread-cert/lowpan.py b/tests/scripts/thread-cert/lowpan.py new file mode 100644 index 000000000..db3ef2bb2 --- /dev/null +++ b/tests/scripts/thread-cert/lowpan.py @@ -0,0 +1,1091 @@ +#!/usr/bin/python +# +# Copyright (c) 2016, 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 collections +import io +import ipaddress +import struct + +import common +import config +import ipv6 + + +class LowpanIPHC: + """ + Class representing a compressed IP header. + + More details: + - URL: https://tools.ietf.org/html/rfc6282 + - section: 3.1. LOWPAN_IPHC Encoding Format + """ + + def __init__(self, tf, nh, hlim, cid, sac, sam, m, dac, dam): + self._tf = tf + self._nh = nh + self._hlim = hlim + self._cid = cid + self._sac = sac + self._sam = sam + self._m = m + self._dac = dac + self._dam = dam + + @property + def tf(self): + return self._tf + + @property + def nh(self): + return self._nh + + @property + def hlim(self): + return self._hlim + + @property + def cid(self): + return self._cid + + @property + def sac(self): + return self._sac + + @property + def sam(self): + return self._sam + + @property + def m(self): + return self._m + + @property + def dac(self): + return self._dac + + @property + def dam(self): + return self._dam + + @classmethod + def from_bytes(cls, data_bytes): + data_byte = data_bytes[0] + + hdr = (data_byte >> 5) & 0x07 + if hdr != 0x03: + raise RuntimeError("Not a 6LowPAN packet.") + + tf = (data_byte >> 3) & 0x03 + nh = (data_byte >> 2) & 0x01 + hlim = data_byte & 0x03 + + data_byte = data_bytes[1] + + cid = (data_byte >> 7) & 0x01 + sac = (data_byte >> 6) & 0x01 + sam = (data_byte >> 4) & 0x03 + m = (data_byte >> 3) & 0x01 + dac = (data_byte >> 2) & 0x01 + dam = data_byte & 0x03 + + return cls(tf, nh, hlim, cid, sac, sam, m, dac, dam) + + def __repr__(self): + return "LowpanIPHC(tf={}, nh={}, hlim={}, cid={}, sac={}, sam={}, m={}, dac={}, dam={})".format(self.tf, self.nh, self.hlim, self.cid, self.sac, self.sam, self.m, self.dac, self.dam) + + +class LowpanNHC: + """ + Class representing a compressed extension header. + + More details: + - URL: https://tools.ietf.org/html/rfc6282 + - section: 4.1. LOWPAN_NHC Format + + """ + + NHC_EID_HOP_BY_HOP = 0 + NHC_EID_ROUTING = 1 + NHC_EID_FRAGMENT = 2 + NHC_EID_DST_OPT = 3 + NHC_EID_MOBILITY = 4 + NHC_EID_IPV6_HDR = 7 + + def __init__(self, eid, nh): + self._eid = eid + self._nh = nh + + @property + def eid(self): + return self._eid + + @property + def nh(self): + return self._nh + + @classmethod + def from_bytes(cls, data_bytes): + header_byte = data_bytes[0] + + eid = (header_byte >> 1) & 0x07 + nh = header_byte & 0x01 + + return cls(eid, nh) + + def __repr__(self): + return "LowpanNHC(eid={}, nh={})".format(self.eid, self.nh) + + +class LowpanUDPHC: + """ + Class representing compressed UDP header. + + More details: + - URL: https://tools.ietf.org/html/rfc6282 + - section: 4.3.3. UDP LOWPAN_NHC Format + + """ + + def __init__(self, c, p): + self._c = c + self._p = p + + @property + def c(self): + return self._c + + @property + def p(self): + return self._p + + @classmethod + def from_bytes(cls, data_bytes): + data_byte = data_bytes[0] + + hdr = (data_byte >> 3) & 0x1f + if hdr != 0x1e: + raise RuntimeError("Not a 6LowPAN UDP header.") + + c = (data_byte >> 2) & 0x01 + p = data_byte & 0x03 + + return cls(c, p) + + def __repr__(self): + return "LowpanUDPHC(c={}, p={})".format(self.c, self.p) + + +class LowpanHopByHopFactory: + + """ Factory that produces HopByHop extension header. """ + + def __init__(self, hop_by_hop_options_factory): + self._hop_by_hop_options_factory = hop_by_hop_options_factory + + def parse(self, data, next_header, message_info): + ext_header_length = ord(data.read(1)) + + ext_header_data = data.read(ext_header_length) + + options = self._hop_by_hop_options_factory.parse(io.BytesIO(ext_header_data), message_info) + + ext_header = ipv6.HopByHop(next_header, options) + + message_info.payload_length += len(ext_header) + + return ext_header + + +class LowpanExtensionHeadersFactory: + + """ Factory that produces extension headers. """ + + NHC_NH_INLINE = 0 + NHC_NH_COMPRESSED = 1 + + def __init__(self, ext_headers_factories): + self._ext_headers_factories = ext_headers_factories if ext_headers_factories is not None else {} + + def _decompress_nh(self, hc, data): + if hc.nh == self.NHC_NH_INLINE: + return ord(data.read(1)) + + elif hc.nh == self.NHC_NH_COMPRESSED: + return None + + def _get_ext_headers_factory(self, eid): + try: + return self._ext_headers_factories[eid] + + except: + raise RuntimeError("Could not find an extension header factory for the EID type: {}".format(eid)) + + def parse(self, data, message_info): + nhc = LowpanNHC.from_bytes(bytearray(data.read(1))) + + next_header = self._decompress_nh(nhc, data) + + factory = self._get_ext_headers_factory(nhc.eid) + + return factory.parse(data, next_header, message_info) + + +class LowpanUdpHeaderFactory: + + """ Factory producing UDP header. """ + + UDP_HC_C_INLINE = 0 + UDP_HC_C_ELIDED = 1 + + UDP_HC_P_BOTH_FULL = 0 + UDP_HC_P_DST_COMPR = 1 + UDP_HC_P_SRC_COMPR = 2 + UDP_HC_P_BOTH_COMPR = 3 + + def _decompress_udp_ports(self, udphc, data): + if udphc.p == self.UDP_HC_P_BOTH_FULL: + src_port = struct.unpack(">H", data.read(2))[0] + dst_port = struct.unpack(">H", data.read(2))[0] + + elif udphc.p == self.UDP_HC_P_DST_COMPR: + src_port = struct.unpack(">H", data.read(2))[0] + dst_port = 0xf000 + ord(data.read(1)) + + elif udphc.p == self.UDP_HC_P_SRC_COMPR: + src_port = 0xf000 + ord(data.read(1)) + dst_port = struct.unpack(">H", data.read(2))[0] + + elif udphc.p == self.UDP_HC_P_BOTH_COMPR: + udp_ports_byte = ord(data.read(1)) + src_port = 0xf0b0 + ((udp_ports_byte >> 4) & 0x0F) + dst_port = 0xf0b0 + (udp_ports_byte & 0x0F) + + return src_port, dst_port + + def _decompress_udp_checksum(self, udphc, data): + if udphc.c == self.UDP_HC_C_INLINE: + checksum = struct.unpack(">H", data.read(2))[0] + + if udphc.c == self.UDP_HC_C_ELIDED: + checksum = 0 + + return checksum + + def parse(self, data, message_info): + udphc = LowpanUDPHC.from_bytes(bytearray(data.read(1))) + + src_port, dst_port = self._decompress_udp_ports(udphc, data) + + checksum = self._decompress_udp_checksum(udphc, data) + + header = ipv6.UDPHeader(src_port, dst_port, checksum=checksum) + + return header + + +class Context(): + + def __init__(self, prefix, prefix_length=None): + if isinstance(prefix, str): + prefix = unicode(prefix) + + prefix, prefix_length = prefix.split("/") + prefix_length = int(prefix_length) + + a = ipaddress.ip_address(prefix) + + self._prefix = bytearray(a.packed) + self._prefix_length = prefix_length + + elif isinstance(prefix, bytearray): + self._prefix = prefix + self._prefix_length = prefix_length if prefix_length is not None else len(self._prefix) * 8 + + @property + def prefix(self): + return self._prefix[:self.prefix_length_all_bytes] + + @property + def prefix_full_bytes(self): + return self._prefix[:self.prefix_length_full_bytes] + + @property + def prefix_length(self): + return self._prefix_length + + @property + def prefix_length_full_bytes(self): + return int(self._prefix_length / 8) + + @property + def prefix_length_rest_bits(self): + return int(self._prefix_length % 8) + + @property + def prefix_length_all_bytes(self): + if self.prefix_length_rest_bits > 0: + return (self.prefix_length_full_bytes + 1) + + return self.prefix_length_full_bytes + + +class ContextManager(dict): + + """ Class representing Context Manager. """ + + def __check_index(self, index): + if index < 0 or index > 15: + raise IndexError("Invalid index: {}. Valid index is in range [0, 15]".format(index)) + + def __check_type(self, value): + if not isinstance(value, Context): + raise TypeError("Invalid value type: {}".format(type(value))) + + def __getitem__(self, index): + self.__check_index(index) + + return super(ContextManager, self).__getitem__(index) + + def __setitem__(self, index, value): + self.__check_index(index) + self.__check_type(value) + + return super(ContextManager, self).__setitem__(index, value) + + +class LowpanIpv6HeaderFactory: + + """ Factory that produces IPv6 header. """ + + IPV6_LINKLOCAL_PREFIX = bytearray([0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + + SHORT_ADDR_PADDING_BYTES = bytearray([0x00, 0x00, 0x00, 0xff, 0xfe, 0x00]) + + IPHC_TF_4B = 0 + IPHC_TF_3B = 1 + IPHC_TF_1B = 2 + IPHC_TF_ELIDED = 3 + + IPHC_NH_INLINE = 0 + IPHC_NH_COMPRESSED = 1 + + IPHC_HLIM_CALCULATE = -1 + IPHC_HLIM_INLINE = 0 + IPHC_HLIM_1 = 1 + IPHC_HLIM_64 = 2 + IPHC_HLIM_255 = 3 + + IPHC_CID_CLEAR = 0 + IPHC_CID_SET = 1 + + IPHC_SAC_STATELESS = 0 + IPHC_SAC_STATEFUL = 1 + + IPHC_SAM_128B = 0 + IPHC_SAM_UNSPECIFIED = 0 + IPHC_SAM_64B = 1 + IPHC_SAM_16B = 2 + IPHC_SAM_0B = 3 + IPHC_SAM_ELIDED = 3 + + IPHC_M_NO = 0 + IPHC_M_YES = 1 + + IPHC_DAC_STATELESS = 0 + IPHC_DAC_STATEFUL = 1 + + IPHC_DAM_128B = 0 + IPHC_DAM_64B = 1 + IPHC_DAM_48B = 1 + IPHC_DAM_32B = 2 + IPHC_DAM_16B = 2 + IPHC_DAM_8B = 3 + IPHC_DAM_0B = 3 + IPHC_DAM_ELIDED = 3 + + IPHC_M_DAM_00 = 0 + IPHC_M_DAM_01 = 1 + IPHC_M_DAM_10 = 2 + IPHC_M_DAM_11 = 3 + + def __init__(self, context_manager=None): + self._context_manager = context_manager + + def _flow_label(self, data_bytes): + flow_label = (data_bytes[0] & 0x0F) << 16 + flow_label += data_bytes[1] << 8 + flow_label += data_bytes[2] + return flow_label + + def _traffic_class(self, dscp, ecn): + return (dscp << 2) | ecn + + def _unpack_dscp(self, data_byte): + return (data_byte & 0x3F) + + def _unpack_ecn(self, data_byte): + return (data_byte >> 6) + + def _decompress_tf_4bytes(self, data): + data_bytes = [ord(b) for b in data.read(4)] + + dscp = self._unpack_dscp(data_bytes[0]) + ecn = self._unpack_ecn(data_bytes[0]) + + traffic_class = self._traffic_class(dscp, ecn) + flow_label = self._flow_label(data_bytes[1:]) + + return traffic_class, flow_label + + def _decompress_tf_3bytes(self, data): + data_bytes = [ord(b) for b in data.read(3)] + + ecn = self._unpack_ecn(data_bytes[0]) + + traffic_class = self._traffic_class(dscp=0, ecn=ecn) + flow_label = self._flow_label(data_bytes) + + return traffic_class, flow_label + + def _decompress_tf_1byte(self, data): + data_byte = ord(data.read(1)) + + dscp = self._unpack_dscp(data_byte) + ecn = self._unpack_ecn(data_byte) + + traffic_class = self._traffic_class(dscp, ecn) + flow_label = 0 + + return traffic_class, flow_label + + def _decompress_tf(self, iphc, data): + if iphc.tf == self.IPHC_TF_4B: + return self._decompress_tf_4bytes(data) + + elif iphc.tf == self.IPHC_TF_3B: + return self._decompress_tf_3bytes(data) + + elif iphc.tf == self.IPHC_TF_1B: + return self._decompress_tf_1byte(data) + + elif iphc.tf == self.IPHC_TF_ELIDED: + return 0, 0 + + def _decompress_nh(self, hc, data): + if hc.nh == self.IPHC_NH_INLINE: + return ord(data.read(1)) + + elif hc.nh == self.IPHC_NH_COMPRESSED: + return None + + def _decompress_hlim(self, iphc, data): + if iphc.hlim == self.IPHC_HLIM_INLINE: + return ord(data.read(1)) + + elif iphc.hlim == self.IPHC_HLIM_1: + return 1 + + elif iphc.hlim == self.IPHC_HLIM_64: + return 64 + + elif iphc.hlim == self.IPHC_HLIM_255: + return 255 + + def _decompress_cid(self, iphc, data): + if iphc.cid == self.IPHC_CID_SET: + cid = ord(data.read(1)) + + sci = (cid >> 4) & 0x0f + dci = cid & 0x0f + + return sci, dci + + elif iphc.cid == self.IPHC_CID_CLEAR: + return 0, 0 + + def _decompress_src_addr_stateless(self, iphc, src_mac_addr, data): + if iphc.sam == self.IPHC_SAM_128B: + return bytearray(data.read(16)) + + elif iphc.sam == self.IPHC_SAM_64B: + return self.IPV6_LINKLOCAL_PREFIX + bytearray(data.read(8)) + + elif iphc.sam == self.IPHC_SAM_16B: + return self.IPV6_LINKLOCAL_PREFIX + self.SHORT_ADDR_PADDING_BYTES + bytearray(data.read(2)) + + elif iphc.sam == self.IPHC_SAM_ELIDED: + return self.IPV6_LINKLOCAL_PREFIX + src_mac_addr.convert_to_iid() + + def _merge_prefix_with_address(self, prefix, prefix_length, address_bytes): + required_bytes = 16 + + prefix_length_full_bytes = int(prefix_length / 8) + prefix_length_rest_bits = int(prefix_length % 8) + + prefix_length_all_bytes = prefix_length_full_bytes + + if prefix_length_rest_bits > 0: + prefix_length_all_bytes += 1 + + # Case in which some bytes overlap + if (prefix_length_all_bytes + len(address_bytes)) > required_bytes: + ############################################################################################### + # Example: + # + # Total address length: 128 bits + # * prefix length: 68 bits + # * address length: 64 bits + # + # overlap: 4 bits ==> the last 4 bits of the address must be replaced by the last 4 bits of prefix + # + # Result: + # +--------------------+---------------------+ + # | prefix (68 bits) | address (64 bits) | + # +--------------------+---------------------+ + ############################################################################################### + + src_addr = prefix[:prefix_length_full_bytes] + required_bytes -= prefix_length_full_bytes + + if prefix_length_rest_bits > 0: + prefix_overlapping_byte = prefix[prefix_length_all_bytes - 1] + address_overlapping_byte = address_bytes[-required_bytes] + + overlapping_byte = prefix_overlapping_byte & ~(0xff >> prefix_length_rest_bits) + overlapping_byte |= address_overlapping_byte & (0xff >> prefix_length_rest_bits) + + src_addr += bytearray([overlapping_byte]) + required_bytes -= 1 + + if required_bytes > 0: + src_addr += address_bytes[-required_bytes:] + + else: + required_bytes -= prefix_length_all_bytes + required_bytes -= len(address_bytes) + + src_addr = prefix[:prefix_length_all_bytes] + bytearray([0x00] * required_bytes) + address_bytes + + return src_addr + + def _decompress_src_addr_stateful(self, iphc, src_mac_addr, sci, data): + if iphc.sam == self.IPHC_SAM_UNSPECIFIED: + return bytearray([0x00] * 16) + + elif iphc.sam == self.IPHC_SAM_64B: + context = self._context_manager[sci] + + return self._merge_prefix_with_address(prefix=context.prefix, + prefix_length=context.prefix_length, + address_bytes=bytearray(data.read(8))) + + elif iphc.sam == self.IPHC_SAM_16B: + context = self._context_manager[sci] + address_bytes = self.SHORT_ADDR_PADDING_BYTES + bytearray(data.read(2)) + + return self._merge_prefix_with_address(prefix=context.prefix, + prefix_length=context.prefix_length, + address_bytes=address_bytes) + + elif iphc.sam == self.IPHC_SAM_0B: + context = self._context_manager[sci] + + return self._merge_prefix_with_address(prefix=context.prefix, + prefix_length=context.prefix_length, + address_bytes=src_mac_addr.convert_to_iid()) + + def _decompress_src_addr(self, iphc, src_mac_addr, sci, data): + if iphc.sac == self.IPHC_SAC_STATELESS: + return self._decompress_src_addr_stateless(iphc, src_mac_addr, data) + + elif iphc.sac == self.IPHC_SAC_STATEFUL: + return self._decompress_src_addr_stateful(iphc, src_mac_addr, sci, data) + + def _decompress_unicast_dst_addr_stateless(self, iphc, dst_mac_addr, data): + if iphc.dam == self.IPHC_DAM_128B: + return bytearray(data.read(16)) + + elif iphc.dam == self.IPHC_DAM_64B: + return self.IPV6_LINKLOCAL_PREFIX + bytearray(data.read(8)) + + elif iphc.dam == self.IPHC_DAM_16B: + return self.IPV6_LINKLOCAL_PREFIX + self.SHORT_ADDR_PADDING_BYTES + bytearray(data.read(2)) + + elif iphc.dam == self.IPHC_DAM_ELIDED: + return self.IPV6_LINKLOCAL_PREFIX + dst_mac_addr.convert_to_iid() + + def _decompress_unicast_dst_addr_stateful(self, iphc, dst_mac_addr, dci, data): + if iphc.dam == self.IPHC_DAM_128B: + raise RuntimeError("Reserved") + + elif iphc.dam == self.IPHC_DAM_64B: + context = self._context_manager[dci] + + return self._merge_prefix_with_address(prefix=context.prefix, + prefix_length=context.prefix_length, + address_bytes=bytearray(data.read(8))) + + elif iphc.dam == self.IPHC_DAM_16B: + context = self._context_manager[dci] + address_bytes = self.SHORT_ADDR_PADDING_BYTES + bytearray(data.read(2)) + + return self._merge_prefix_with_address(prefix=context.prefix, + prefix_length=context.prefix_length, + address_bytes=address_bytes) + + elif iphc.dam == self.IPHC_DAM_0B: + context = self._context_manager[dci] + + return self._merge_prefix_with_address(prefix=context.prefix, + prefix_length=context.prefix_length, + address_bytes=dst_mac_addr.convert_to_iid()) + + def _decompress_unicast_dst_addr(self, iphc, dst_mac_addr, dci, data): + if iphc.dac == self.IPHC_DAC_STATELESS: + return self._decompress_unicast_dst_addr_stateless(iphc, dst_mac_addr, data) + + elif iphc.dac == self.IPHC_DAC_STATEFUL: + return self._decompress_unicast_dst_addr_stateful(iphc, dst_mac_addr, dci, data) + + def _decompress_multicast_dst_addr_stateless(self, iphc, data): + if iphc.dam == self.IPHC_DAM_128B: + return bytearray(data.read(16)) + + elif iphc.dam == self.IPHC_DAM_48B: + addr48b = bytearray(data.read(6)) + return bytearray([0xff, addr48b[0]]) + bytearray([0x00] * 9) + addr48b[1:] + + elif iphc.dam == self.IPHC_DAM_32B: + addr32b = bytearray(data.read(4)) + return bytearray([0xFF, addr32b[0]]) + bytearray([0x00] * 11) + addr32b[1:] + + elif iphc.dam == self.IPHC_DAM_8B: + return bytearray([0xFF, 0x02]) + bytearray([0x00] * 13) + data.read(1) + + def _decompress_multicast_dst_addr_stateful(self, iphc, dci, data): + if iphc.dam == self.IPHC_M_DAM_00: + context = self._context_manager[dci] + + addr48b = bytearray(data.read(6)) + + p_bytes_count = 8 + + prefix = context.prefix[:p_bytes_count] + prefix_length = context.prefix_length + + missing_bytes = p_bytes_count - len(prefix) + + if missing_bytes > 0: + prefix += bytearray([0x00] * missing_bytes) + + return bytearray([0xff]) + addr48b[:2] + bytearray([prefix_length]) + prefix + addr48b[2:] + + elif iphc.dam == self.IPHC_M_DAM_01: + raise RuntimeError("Reserved") + + elif iphc.dam == self.IPHC_M_DAM_10: + raise RuntimeError("Reserved") + + elif iphc.dam == self.IPHC_M_DAM_11: + raise RuntimeError("Reserved") + + def _decompress_multicast_dst_addr(self, iphc, dci, data): + if iphc.dac == self.IPHC_DAC_STATELESS: + return self._decompress_multicast_dst_addr_stateless(iphc, data) + + elif iphc.dac == self.IPHC_DAC_STATEFUL: + return self._decompress_multicast_dst_addr_stateful(iphc, dci, data) + + def _decompress_dst_addr(self, iphc, dst_mac_addr, dci, data): + if iphc.m == self.IPHC_M_NO: + return self._decompress_unicast_dst_addr(iphc, dst_mac_addr, dci, data) + + elif iphc.m == self.IPHC_M_YES: + return self._decompress_multicast_dst_addr(iphc, dci, data) + + def parse(self, data, message_info): + iphc = LowpanIPHC.from_bytes(bytearray(data.read(2))) + + sci, dci = self._decompress_cid(iphc, data) + + traffic_class, flow_label = self._decompress_tf(iphc, data) + + next_header = self._decompress_nh(iphc, data) + + hop_limit = self._decompress_hlim(iphc, data) + + src_address = self._decompress_src_addr(iphc, message_info.source_mac_address, sci, data) + + dst_address = self._decompress_dst_addr(iphc, message_info.destination_mac_address, dci, data) + + header = ipv6.IPv6Header(src_address, dst_address, traffic_class, flow_label, hop_limit) + + header.next_header = next_header + + return header + + +class LowpanDecompressor: + + """ Class decompressing 6LoWPAN packets. """ + + def __init__(self, lowpan_ip_header_factory, lowpan_extension_headers_factory, lowpan_udp_header_factory): + self._lowpan_ip_header_factory = lowpan_ip_header_factory + self._lowpan_extension_headers_factory = lowpan_extension_headers_factory + self._lowpan_udp_header_factory = lowpan_udp_header_factory + + def _is_ipv6_extension_header(self, header_first_byte): + return ((header_first_byte >> 4) & 0x0f) == 0x0e + + def _is_udp_header(self, header_first_byte): + return ((header_first_byte >> 4) & 0x0f) == 0x0f + + def _peek_n_bytes(self, data, n): + read_data = data.read(n) + data.seek(-n, io.SEEK_CUR) + return read_data + + def _is_next_header_compressed(self, header): + return (header.next_header is None) + + def decompress(self, data, message_info): + ipv6_header = self._lowpan_ip_header_factory.parse(data, message_info) + + previous_header = ipv6_header + + extension_headers = [] + udp_header = None + + if self._is_next_header_compressed(ipv6_header): + + while data.tell() < len(data.getvalue()): + header_first_byte = ord(self._peek_n_bytes(data, 1)) + + if self._is_ipv6_extension_header(header_first_byte): + extension_header = self._lowpan_extension_headers_factory.parse(data, message_info) + extension_headers.append(extension_header) + + # Update next header field in the previous header + previous_header.next_header = extension_header.type + previous_header = extension_header + + if not self._is_next_header_compressed(extension_header): + # There is not more compressed headers + break + + elif self._is_udp_header(header_first_byte): + udp_header = self._lowpan_udp_header_factory.parse(data, message_info) + + # Update next header field in the previous header + previous_header.next_header = udp_header.type + + # There is not more headers after UDP header + break + + return ipv6_header, extension_headers, udp_header + + +class LowpanMeshHeader(object): + + """ Class representing 6LoWPAN mesh header (RFC 4944 5.2). """ + + def __init__(self, hops_left, originator_address, final_destination_address): + self._hops_left = hops_left + self._originator_address = originator_address + self._final_destination_address = final_destination_address + + @property + def hops_left(self): + return self._hops_left + + @property + def originator_address(self): + return self._originator_address + + @property + def final_destination_address(self): + return self._final_destination_address + + +class LowpanMeshHeaderFactory: + + def _parse_address(self, data, is_short): + if is_short: + return common.MacAddress.from_rloc16(bytearray(data.read(2))) + else: + return common.MacAddress.from_eui64(bytearray(data.read(8))) + + def parse(self, data, message_info): + data_byte = ord(data.read(1)) + + is_short_originator_address = bool(data_byte & 0x20) + is_short_final_destination_address = bool(data_byte & 0x10) + + hops_left = (data_byte & 0x0f) + originator_address = self._parse_address(data, is_short_originator_address) + final_destination_address = self._parse_address(data, is_short_final_destination_address) + + return LowpanMeshHeader(hops_left, originator_address, final_destination_address) + + +class LowpanFragmentationHeader(object): + + def __init__(self, datagram_size, datagram_tag, datagram_offset=0): + self._datagram_size = datagram_size + self._datagram_tag = datagram_tag + self._datagram_offset = datagram_offset + + @property + def datagram_size(self): + return self._datagram_size + + @property + def datagram_tag(self): + return self._datagram_tag + + @property + def datagram_offset(self): + return self._datagram_offset + + @property + def is_first(self): + return self.datagram_offset == 0 + + @classmethod + def from_bytes(cls, data): + datagram_size = struct.unpack(">H", data.read(2))[0] + has_offset = ((datagram_size >> 11) & 0x1f) == 0x1c + + datagram_size &= 0x7ff + datagram_tag = struct.unpack(">H", data.read(2))[0] + datagram_offset = 0 + + if has_offset: + datagram_offset = ord(data.read(1)) + + return cls(datagram_size, datagram_tag, datagram_offset) + + +class LowpanFragmentsBuffer(object): + + def __init__(self, buffer_size): + self._buffer = [None] * buffer_size + self._position = 0 + + def write(self, data): + if (self._position + len(data)) > len(self._buffer): + raise ValueError("Write failure. Data length is bigger than the destination buffer length.") + + for i, byte in enumerate(data): + self._buffer[self._position + i] = byte + + self._position += len(data) + return len(data) + + def seek(self, offset): + if offset >= len(self._buffer): + raise ValueError("Could not seek current offset. Offset value is bigger than the buffer length.") + + self._position = offset + + def tell(self): + return self._position + + def whole_packet_received(self): + return all([byte is not None for byte in self._buffer]) + + def read(self): + if not self.whole_packet_received(): + raise ValueError("Only a part of the packet has been stored in the buffer.") + + return bytearray(self._buffer) + + def __len__(self): + return len(self._buffer) + + +class LowpanFragmentsBuffersManager(object): + + def __init__(self): + self._fragments_buffers = {} + + def _create_key(self, message_info, datagram_tag): + key = bytes(message_info.source_mac_address.mac_address) +\ + bytes(message_info.destination_mac_address.mac_address) +\ + bytes(datagram_tag) + return key + + def _allocate_fragments_buffer(self, key, datagram_size): + if datagram_size is None or datagram_size < 0: + raise ValueError("Could not allocate fragments buffer. Invalid datagram size: {}".format(datagram_size)) + + fragments_buffer = LowpanFragmentsBuffer(datagram_size) + + self._fragments_buffers[key] = fragments_buffer + return fragments_buffer + + def get_fragments_buffer(self, message_info, datagram_tag, datagram_size=None): + key = self._create_key(message_info, datagram_tag) + + if not key in self._fragments_buffers: + self._allocate_fragments_buffer(key, datagram_size) + + return self._fragments_buffers[key] + + def free_fragments_buffer(self, message_info, datagram_tag): + key = self._create_key(message_info, datagram_tag) + + del self._fragments_buffers[key] + + +class LowpanParser(object): + + def __init__(self, lowpan_mesh_header_factory, lowpan_decompressor, lowpan_fragements_buffers_manager, ipv6_packet_factory): + self._lowpan_mesh_header_factory = lowpan_mesh_header_factory + self._lowpan_decompressor = lowpan_decompressor + self._lowpan_fragments_buffers_manager = lowpan_fragements_buffers_manager + self._ipv6_packet_factory = ipv6_packet_factory + + def _peek_n_bytes(self, data, n): + data_bytes = data.read(n) + data.seek(-n, io.SEEK_CUR) + return data_bytes + + def _is_mesh_header(self, first_byte): + return (((first_byte >> 6) & 0x03) == 0x02) + + def _is_first_fragmentation_header(self, first_byte): + return (((first_byte >> 3) & 0x1f) == 0x18) + + def _is_subsequent_fragmentation_header(self, first_byte): + return (((first_byte >> 3) & 0x1f) == 0x1c) + + def _is_iphc(self, first_byte): + return (((first_byte >> 5) & 0x07) == 0x03) + + def _decompress_iphc(self, data, message_info): + return self._lowpan_decompressor.decompress(data, message_info) + + def _handle_first_fragmentation_header(self, data, message_info): + fragmentation_header = LowpanFragmentationHeader.from_bytes(data) + + fragments_buffer = self._lowpan_fragments_buffers_manager.get_fragments_buffer( + message_info, fragmentation_header.datagram_tag, fragmentation_header.datagram_size) + + ipv6_header, extension_headers, udp_header = self._decompress_iphc(data, message_info) + + uncompressed_data = data.read() + + # Update payload lengths + ipv6_header.payload_length = fragmentation_header.datagram_size - len(ipv6_header) + + fragments_buffer.seek(0) + fragments_buffer.write(ipv6_header.to_bytes()) + + for extension_header in extension_headers: + fragments_buffer.write(extension_header.to_bytes()) + + if udp_header is not None: + fragments_buffer.write(udp_header.to_bytes()) + + fragments_buffer.write(uncompressed_data) + + if fragments_buffer.whole_packet_received(): + data = io.BytesIO(fragments_buffer.read()) + + self._lowpan_fragments_buffers_manager.free_fragments_buffer(message_info, + fragmentation_header.datagram_tag) + + return self._ipv6_packet_factory.parse(data, message_info) + + return None + + def _handle_subsequent_fragmentation_header(self, data, message_info): + fragmentation_header = LowpanFragmentationHeader.from_bytes(data) + + fragments_buffer = self._lowpan_fragments_buffers_manager.get_fragments_buffer( + message_info, fragmentation_header.datagram_tag, fragmentation_header.datagram_size) + + offset = (fragmentation_header.datagram_offset * 8) + + fragments_buffer.seek(offset) + fragments_buffer.write(data.read()) + + if fragments_buffer.whole_packet_received(): + data = io.BytesIO(fragments_buffer.read()) + + self._lowpan_fragments_buffers_manager.free_fragments_buffer(message_info, + fragmentation_header.datagram_tag) + + return self._ipv6_packet_factory.parse(data, message_info) + + return None + + def _handle_iphc_header(self, data, message_info): + ipv6_header, extension_headers, udp_header = self._decompress_iphc(data, message_info) + + uncompressed_data = data.read() + + decompressed_data = bytearray([]) + + for extension_header in extension_headers: + decompressed_data += extension_header.to_bytes() + + if udp_header is not None: + udp_header.payload_length = len(uncompressed_data) + + decompressed_data += udp_header.to_bytes() + + decompressed_data += uncompressed_data + + ipv6_header.payload_length = len(decompressed_data) + + decompressed_data = ipv6_header.to_bytes() + decompressed_data + + return self._ipv6_packet_factory.parse(io.BytesIO(decompressed_data), message_info) + + def parse(self, data, message_info): + + while data.tell() < len(data.getvalue()): + first_byte = ord(self._peek_n_bytes(data, n=1)) + + if self._is_mesh_header(first_byte): + mesh_header = self._lowpan_mesh_header_factory.parse(data, message_info) + + message_info.source_mac_address = mesh_header.originator_address + message_info.destination_mac_address = mesh_header.final_destination_address + + elif self._is_first_fragmentation_header(first_byte): + return self._handle_first_fragmentation_header(data, message_info) + + elif self._is_subsequent_fragmentation_header(first_byte): + return self._handle_subsequent_fragmentation_header(data, message_info) + + elif self._is_iphc(first_byte): + return self._handle_iphc_header(data, message_info) diff --git a/tests/scripts/thread-cert/mac802154.py b/tests/scripts/thread-cert/mac802154.py new file mode 100644 index 000000000..6969fef5e --- /dev/null +++ b/tests/scripts/thread-cert/mac802154.py @@ -0,0 +1,263 @@ +#!/usr/bin/python +# +# Copyright (c) 2016, 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. +# + +""" + This module provides simple 802.15.4 MAC parser. +""" + +import io +import struct + +import config +from common import MacAddress, MessageInfo +from net_crypto import AuxiliarySecurityHeader, CryptoEngine, MacCryptoMaterialCreator + + +class DeviceDescriptors: + + """Class representing 802.15.4 Device Descriptors.""" + + device_descriptors = {} + + @classmethod + def add(cls, short_address, extended_address): + short_address = cls._get_short_address_value(short_address) + cls.device_descriptors[short_address] = extended_address + + @classmethod + def get_extended(cls, short_address): + short_address = cls._get_short_address_value(short_address) + return cls.device_descriptors[short_address] + + @staticmethod + def _get_short_address_value(short_address): + if isinstance(short_address, MacAddress): + short_address = short_address.rloc + return short_address + + +class MacHeader: + + """Class representing 802.15.4 MAC header.""" + + class FrameType: + BEACON = 0 + DATA = 1 + ACK = 2 + COMMAND = 3 + + class AddressMode: + NOT_PRESENT = 0 + SHORT = 2 + EXTENDED = 3 + + def __init__(self, frame_type, frame_pending, ack_request, frame_version, seq, + dest_pan_id=None, dest_address=None, src_pan_id=None, src_address=None, command_type=None, + aux_sec_header=None, mic=None, + fcs=None): + + self.frame_type = frame_type + self.frame_pending = frame_pending + self.ack_request = ack_request + self.frame_version = frame_version + self.seq = seq + + self.dest_pan_id = dest_pan_id + self.dest_address = dest_address + self.src_pan_id = src_pan_id + self.src_address = src_address + + self.aux_sec_header = aux_sec_header + self.mic = mic + + self.fcs = fcs + + +class MacPayload: + + """Class representing 802.15.4 MAC payload.""" + + def __init__(self, data): + self.data = bytearray(data) + + +class MacFrame: + + """Class representing 802.15.4 MAC frame.""" + + def parse(self, data): + mhr_start = data.tell() + + fc, seq = struct.unpack("> 10 + frame_version = (fc & 0x3000) >> 12 + source_addr_mode = (fc & 0xc000) >> 14 + + if frame_type == MacHeader.FrameType.ACK: + fcs = self._parse_fcs(data, data.tell()) + self.header = MacHeader(frame_type, frame_pending, ack_request, frame_version, seq, fcs=fcs) + self.payload = None + return + + # Presence of PAN Ids is not fully implemented yet but should be enough for Thread. + dest_pan_id = struct.unpack("> 3 + + if key_id_mode == 1: + key_id = data.read(1) + elif key_id_mode == 2: + key_id = data.read(5) + elif key_id_mode == 3: + key_id = data.read(9) + else: + key_source = None + key_index = None + + return AuxiliarySecurityHeader(key_id_mode, security_level, frame_counter, key_id, big_endian=False) + + def _parse_mic(self, data, security_level): + if security_level in (1, 5): + data.seek(-4, io.SEEK_CUR) + payload_end = data.tell() + mic = data.read(4) + elif security_level in (2, 6): + data.seek(-8, io.SEEK_CUR) + payload_end = data.tell() + mic = data.read(8) + elif security_level in (3, 7): + data.seek(-16, io.SEEK_CUR) + payload_end = data.tell() + mic = data.read(16) + else: + payload_end = data.tell() + + return mic, payload_end + + def _parse_fcs(self, data, fcs_start): + data.seek(fcs_start) + fcs = bytearray(data.read(2)) + return fcs diff --git a/tests/scripts/thread-cert/message.py b/tests/scripts/thread-cert/message.py new file mode 100644 index 000000000..616c99b1d --- /dev/null +++ b/tests/scripts/thread-cert/message.py @@ -0,0 +1,301 @@ +#!/usr/bin/python +# +# Copyright (c) 2016, 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 io +import ipaddress +import struct + +import common +import ipv6 +import lowpan +import mac802154 +import mle + + +MessageType = common.enum("MLE", + "COAP", + "ICMP", + "ACK", + "BEACON", + "DATA") + + +class Message(object): + + def __init__(self): + self._type = None + self._channel = None + self._mac_header = None + self._ipv6_packet = None + self._mle = None + self._icmp = None + + def _extract_udp_datagram(self, udp_datagram): + if isinstance(udp_datagram.payload, mle.MleMessage): + self._type = MessageType.MLE + self._mle = udp_datagram.payload + + # TODO: Initial version doesn't support CoAP + elif isinstance(udp_datagram.payload, ipv6.UDPBytesPayload): + self._type = MessageType.COAP + self._coap = udp_datagram.payload + + def _extract_upper_layer_protocol(self, upper_layer_protocol): + if isinstance(upper_layer_protocol, ipv6.ICMPv6): + self._type = MessageType.ICMP + self._icmp = upper_layer_protocol + + elif isinstance(upper_layer_protocol, ipv6.UDPDatagram): + self._extract_udp_datagram(upper_layer_protocol) + + @property + def type(self): + return self._type + + @type.setter + def type(self, value): + self._type = value + + @property + def channel(self): + return self._channel + + @channel.setter + def channel(self, value): + self._channel = value + + @property + def mac_header(self): + return self._mac_header + + @mac_header.setter + def mac_header(self, value): + self._mac_header = value + + if self._mac_header.frame_type == mac802154.MacHeader.FrameType.BEACON: + self._type = MessageType.BEACON + + elif self._mac_header.frame_type == mac802154.MacHeader.FrameType.ACK: + self._type = MessageType.ACK + + elif self._mac_header.frame_type == mac802154.MacHeader.FrameType.DATA: + self._type = MessageType.DATA + + @property + def ipv6_packet(self): + return self._ipv6_packet + + @ipv6_packet.setter + def ipv6_packet(self, value): + self._ipv6_packet = value + self._extract_upper_layer_protocol(value.upper_layer_protocol) + + @property + def mle(self): + return self._mle + + @mle.setter + def mle(self, value): + self._mle = value + + @property + def icmp(self): + return self._icmp + + @icmp.setter + def icmp(self, value): + self._icmp = value + + def get_mle_message_tlv(self, tlv_class_type): + if self.type != MessageType.MLE: + raise ValueError("Invalid message type. Expected MLE message.") + + for tlv in self.mle.command.tlvs: + if isinstance(tlv, tlv_class_type): + return tlv + + def assertMleMessageIsType(self, command_type): + if self.type != MessageType.MLE: + raise ValueError("Invalid message type. Expected MLE message.") + + assert(self.mle.command.type == command_type) + + def assertMleMessageContainsTlv(self, tlv_class_type): + if self.type != MessageType.MLE: + raise ValueError("Invalid message type. Expected MLE message.") + + contains_tlv = False + for tlv in self.mle.command.tlvs: + if isinstance(tlv, tlv_class_type): + contains_tlv = True + break + + assert(contains_tlv == True) + + def assertMleMessageDoesNotContainTlv(self, tlv_class_type): + if self.type != MessageType.MLE: + raise ValueError("Invalid message type. Expected MLE message.") + + contains_tlv = False + for tlv in self.mle.command.tlvs: + if isinstance(tlv, tlv_class_type): + contains_tlv = True + break + + assert(contains_tlv == False) + + def assertMleMessageContainsOptionalTlv(self, tlv_class_type): + if self.type != MessageType.MLE: + raise ValueError("Invalid message type. Expected MLE message.") + + contains_tlv = False + for tlv in self.mle.command.tlvs: + if isinstance(tlv, tlv_class_type): + contains_tlv = True + break + + print("MleMessage doesn't contain optional TLV: {}".format(tlv_class_type)) + + def assertSentToNode(self, node): + sent_to_node = False + dst_addr = self.ipv6_packet.ipv6_header.destination_address + + for addr in node.get_addrs(): + if dst_addr == ipaddress.ip_address(addr): + sent_to_node = True + + assert sent_to_node == True + + def assertSentToDestinationAddress(self, ipv6_address): + assert self.ipv6_packet.ipv6_header.destination_address == ipaddress.ip_address(unicode(ipv6_address)) + + def assertSentWithHopLimit(self, hop_limit): + assert self.ipv6_packet.ipv6_header.hop_limit == hop_limit + + def __repr__(self): + return "Message(type={})".format(MessageType.name[self.type]) + + +class MessagesSet(object): + + def __init__(self, messages): + self._messages = messages + + @property + def messages(self): + return self._messages + + def next_mle_message(self, command_type, assert_enabled=True): + message = self.next_mle_message_of_one_of_command_types(command_type,) + + if assert_enabled: + assert message is not None, "Could not find MleMessage of the type: {}".format(command_type) + + return message + + def next_mle_message_of_one_of_command_types(self, *command_types): + message = None + + while self.messages: + m = self.messages.pop(0) + + if m.type != MessageType.MLE: + continue + + command_found = False + + for command_type in command_types: + if m.mle.command.type == command_type: + command_found = True + break + + if command_found: + message = m + break + + return message + + def contains_mle_message(self, command_type): + for m in self.messages: + if m.type != MessageType.MLE: + continue + + if m.mle.command.type == command_type: + return True + + return False + + +class MessageFactory: + + def __init__(self, lowpan_parser): + self._lowpan_parser = lowpan_parser + + def _add_device_descriptors(self, message): + for tlv in message.mle.command.tlvs: + + if isinstance(tlv, mle.SourceAddress): + mac802154.DeviceDescriptors.add(tlv.address, message.mac_header.src_address) + + if isinstance(tlv, mle.Address16): + mac802154.DeviceDescriptors.add(tlv.address, message.mac_header.dest_address) + + def _parse_mac_frame(self, data): + mac_frame = mac802154.MacFrame() + mac_frame.parse(data) + return mac_frame + + def create(self, data): + message = Message() + message.channel = struct.unpack(">B", data.read(1)) + + # Parse MAC header + mac_frame = self._parse_mac_frame(data) + message.mac_header = mac_frame.header + + if message.mac_header.frame_type != mac802154.MacHeader.FrameType.DATA: + return message + + message_info = common.MessageInfo() + message_info.source_mac_address = message.mac_header.src_address + message_info.destination_mac_address = message.mac_header.dest_address + + # Create stream with 6LoWPAN datagram + lowpan_payload = io.BytesIO(mac_frame.payload.data) + + ipv6_packet = self._lowpan_parser.parse(lowpan_payload, message_info) + if ipv6_packet is None: + return message + + message.ipv6_packet = ipv6_packet + + if message.type == MessageType.MLE: + self._add_device_descriptors(message) + + return message diff --git a/tests/scripts/thread-cert/mle.py b/tests/scripts/thread-cert/mle.py new file mode 100644 index 000000000..26064432b --- /dev/null +++ b/tests/scripts/thread-cert/mle.py @@ -0,0 +1,1170 @@ +#!/usr/bin/python +# +# Copyright (c) 2016, 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 io +import ipaddress +import struct + +from binascii import hexlify + +import common +import network_data + +CommandType = common.enum(LINK_REQUEST=0, + LINK_ACCEPT=1, + LINK_ACCEPT_AND_REQUEST=2, + LINK_REJECT=3, + ADVERTISEMENT=4, + UPDATE=5, + UPDATE_REQUEST=6, + DATA_REQUEST=7, + DATA_RESPONSE=8, + PARENT_REQUEST=9, + PARENT_RESPONSE=10, + CHILD_ID_REQUEST=11, + CHILD_ID_RESPONSE=12, + CHILD_UPDATE_REQUEST=13, + CHILD_UPDATE_RESPONSE=14, + ANNOUNCE=15, + DISCOVERY_REQUEST=16, + DISCOVERY_RESPONSE=17 + ) + +TlvType = common.enum(SOURCE_ADDRESS=0, + MODE=1, + TIMEOUT=2, + CHALLENGE=3, + RESPONSE=4, + LINK_LAYER_FRAME_COUNTER=5, + MLE_FRAME_COUNTER=8, + ROUTE64=9, + ADDRESS16=10, + LEADER_DATA=11, + NETWORK_DATA=12, + TLV_REQUEST=13, + SCAN_MASK=14, + CONNECTIVITY=15, + LINK_MARGIN=16, + STATUS=17, + VERSION=18, + ADDRESS_REGISTRATION=19, + CHANNEL=20, + PANID=21, + ACTIVE_TIMESTAMP=22, + PENDING_TIMESTAMP=23, + ACTIVE_OPERATIONAL_DATASET=24, + PENDING_OPERATIONAL_DATASET=25, + THREAD_DISCOVERY=26 + ) + + +class SourceAddress(object): + + def __init__(self, address): + self._address = address + + @property + def address(self): + return self._address + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.address == other.address + + def __repr__(self): + return "SourceAddress(address={})".format(hex(self._address)) + + +class SourceAddressFactory: + + def parse(self, data, message_info): + address = struct.unpack(">H", data.read(2))[0] + return SourceAddress(address) + + +class Mode(object): + + def __init__(self, receiver, secure, device_type, network_data): + self._receiver = receiver + self._secure = secure + self._device_type = device_type + self._network_data = network_data + + @property + def receiver(self): + return self._receiver + + @property + def secure(self): + return self._secure + + @property + def device_type(self): + return self._device_type + + @property + def network_data(self): + return self._network_data + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.receiver == other.receiver and self.secure == other.secure and \ + self.device_type == other.device_type and self.network_data == other.network_data + + def __repr__(self): + return "Mode(receiver={}, secure={}, device_type={}, network_data={})".format(self.receiver, + self.secure, + self.device_type, + self.network_data) + + +class ModeFactory: + + def parse(self, data, message_info): + mode = ord(data.read(1)) + receiver = (mode >> 3) & 0x01 + secure = (mode >> 2) & 0x01 + device_type = (mode >> 1) & 0x01 + network_data = (mode >> 0) & 0x01 + return Mode(receiver, secure, device_type, network_data) + + +class Timeout(object): + + def __init__(self, timeout): + self._timeout = timeout + + @property + def timeout(self): + return self._timeout + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.timeout == other.timeout + + def __repr__(self): + return "Timeout(timeout={})".format(self.timeout) + + +class TimeoutFactory: + + def parse(self, data, message_info): + timeout = struct.unpack(">I", data.read(4))[0] + return Timeout(timeout) + + +class Challenge(object): + + def __init__(self, challenge): + self._challenge = challenge + + @property + def challenge(self): + return self._challenge + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.challenge == other.challenge + + def __repr__(self): + return "Challenge(challenge=b'{}')".format(hexlify(self.challenge)) + + +class ChallengeFactory: + + def parse(self, data, message_info): + challenge = data.read() + return Challenge(challenge) + + +class Response(object): + + def __init__(self, response): + self._response = response + + @property + def response(self): + return self._response + + def __eq__(self, other): + if not isinstance(other, Response): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.response == other.response + + def __repr__(self): + return "Response(response=b'{}')".format(hexlify(self.response)) + + +class ResponseFactory: + + def parse(self, data, message_info): + response = data.read() + return Response(response) + + +class LinkLayerFrameCounter(object): + + def __init__(self, frame_counter): + self._frame_counter = frame_counter + + @property + def frame_counter(self): + return self._frame_counter + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.frame_counter == other.frame_counter + + def __repr__(self): + return "LinkLayerFrameCounter(frame_counter={})".format(self.frame_counter) + + +class LinkLayerFrameCounterFactory: + + def parse(self, data, message_info): + frame_counter = struct.unpack(">I", data.read(4))[0] + return LinkLayerFrameCounter(frame_counter) + + +class MleFrameCounter(object): + + def __init__(self, frame_counter): + self._frame_counter = frame_counter + + @property + def frame_counter(self): + return self._frame_counter + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.frame_counter == other.frame_counter + + def __repr__(self): + return "MleFrameCounter(frame_counter={})".format(self.frame_counter) + + +class MleFrameCounterFactory: + + def parse(self, data, message_info): + frame_counter = struct.unpack(">I", data.read(4))[0] + return MleFrameCounter(frame_counter) + + +class LinkQualityAndRouteData(object): + + def __init__(self, output, _input, route): + self._output = output + self._input = _input + self._route = route + + @property + def output(self): + return self._output + + @property + def input(self): + return self._input + + @property + def route(self): + return self._route + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.output == other.output and self.input == other.input and self.route == other.route + + def __repr__(self): + return "LinkQualityAndRouteData(ouput={}, input={}, route={})".format(self.output, self.input, self.route) + + +class LinkQualityAndRouteDataFactory: + + def parse(self, data, message_info): + lqrd = ord(data.read(1)) + output = (lqrd >> 6) & 0x3 + _input = (lqrd >> 4) & 0x3 + route = lqrd & 0x0f + return LinkQualityAndRouteData(output, _input, route) + + +class Route64(object): + + def __init__(self, id_sequence, router_id_mask, link_quality_and_route_data): + self._id_sequence = id_sequence + self._router_id_mask = router_id_mask + self._link_quality_and_route_data = link_quality_and_route_data + + @property + def id_sequence(self): + return self._id_sequence + + @property + def router_id_mask(self): + return self._router_id_mask + + @property + def link_quality_and_route_data(self): + return self._link_quality_and_route_data + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.id_sequence == other.id_sequence and self.router_id_mask == other.router_id_mask and \ + self.link_quality_and_route_data == other.link_quality_and_route_data + + def __repr__(self): + lqrd_str = ", ".join(["{}".format(lqrd) for lqrd in self.link_quality_and_route_data]) + return "Route64(id_sequence={}, router_id_mask={}, link_quality_and_route_data=[{}])".format( + self.id_sequence, hex(self.router_id_mask), lqrd_str) + + +class Route64Factory: + + def __init__(self, link_quality_and_route_data_factory): + self._lqrd_factory = link_quality_and_route_data_factory + + def parse(self, data, message_info): + id_sequence = ord(data.read(1)) + router_id_mask = struct.unpack(">Q", data.read(8))[0] + + link_quality_and_route_data = [] + + while data.tell() < len(data.getvalue()): + link_quality_and_route_data.append(self._lqrd_factory.parse(data, message_info)) + + return Route64(id_sequence, router_id_mask, link_quality_and_route_data) + + +class Address16(object): + + def __init__(self, address): + self._address = address + + @property + def address(self): + return self._address + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.address == other.address + + def __repr__(self): + return "Address16(address={})".format(hex(self.address)) + + +class Address16Factory: + + def parse(self, data, message_info): + address = struct.unpack(">H", data.read(2))[0] + return Address16(address) + + +class LeaderData(object): + + def __init__(self, partition_id, weighting, data_version, stable_data_version, leader_router_id): + self._partition_id = partition_id + self._weighting = weighting + self._data_version = data_version + self._stable_data_version = stable_data_version + self._leader_router_id = leader_router_id + + @property + def partition_id(self): + return self._partition_id + + @property + def weighting(self): + return self._weighting + + @property + def data_version(self): + return self._data_version + + @property + def stable_data_version(self): + return self._stable_data_version + + @property + def leader_router_id(self): + return self._leader_router_id + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.partition_id == other.partition_id and \ + self.weighting == other.weighting and \ + self.data_version == other.data_version and \ + self.stable_data_version == other.stable_data_version and \ + self.leader_router_id == other.leader_router_id + + def __repr__(self): + return "LeaderData(partition_id={}, weighting={}, data_version={}, stable_data_version={}, leader_router_id={})".format( + self.partition_id, self.weighting, self.data_version, self.stable_data_version, self.leader_router_id) + + +class LeaderDataFactory: + + def parse(self, data, message_info): + partition_id = struct.unpack(">I", data.read(4))[0] + weighting = ord(data.read(1)) + data_version = ord(data.read(1)) + stable_data_version = ord(data.read(1)) + leader_router_id = ord(data.read(1)) + return LeaderData(partition_id, weighting, data_version, stable_data_version, leader_router_id) + + +class NetworkData(object): + + def __init__(self, tlvs): + self._tlvs = tlvs + + @property + def tlvs(self): + return self._tlvs + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.tlvs == other.tlvs + + def __repr__(self): + tlvs_str = ", ".join(["{}".format(tlv) for tlv in self.tlvs]) + return "NetworkData(tlvs=[{}])".format(tlvs_str) + + +class NetworkDataFactory: + + def __init__(self, network_data_tlvs_factory): + self._tlvs_factory = network_data_tlvs_factory + + def parse(self, data, message_info): + tlvs = self._tlvs_factory.parse(data, message_info) + return NetworkData(tlvs) + + +class TlvRequest(object): + + def __init__(self, tlvs): + self._tlvs = tlvs + + @property + def tlvs(self): + return self._tlvs + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.tlvs == other.tlvs + + def __repr__(self): + tlvs_str = ", ".join(["{}".format(tlv) for tlv in self.tlvs]) + return "TlvRequest(tlvs=[{}])".format(tlvs_str) + + +class TlvRequestFactory: + + def parse(self, data, message_info): + tlvs = [ord(b) for b in data.read()] + return TlvRequest(tlvs) + + +class ScanMask(object): + + def __init__(self, router, end_device): + self._router = router + self._end_device = end_device + + @property + def router(self): + return self._router + + @property + def end_device(self): + return self._end_device + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.router == other.router and self.end_device == other.end_device + + def __repr__(self): + return "ScanMask(router={}, end_device={})".format(self.router, self.end_device) + + +class ScanMaskFactory: + + def parse(self, data, message_info): + scan_mask = ord(data.read(1)) + router = (scan_mask >> 7) & 0x01 + end_device = (scan_mask >> 6) & 0x01 + return ScanMask(router, end_device) + + +class Connectivity(object): + + def __init__(self, + pp, + link_quality_3, + link_quality_2, + link_quality_1, + leader_cost, + id_sequence, + active_routers, + sed_buffer_size=None, + sed_datagram_count=None): + self._pp = pp + self._link_quality_3 = link_quality_3 + self._link_quality_2 = link_quality_2 + self._link_quality_1 = link_quality_1 + self._leader_cost = leader_cost + self._id_sequence = id_sequence + self._active_routers = active_routers + self._sed_buffer_size = sed_buffer_size + self._sed_datagram_count = sed_datagram_count + + @property + def pp(self): + return self._pp + + @property + def link_quality_3(self): + return self._link_quality_3 + + @property + def link_quality_2(self): + return self._link_quality_2 + + @property + def link_quality_1(self): + return self._link_quality_1 + + @property + def leader_cost(self): + return self._leader_cost + + @property + def id_sequence(self): + return self._id_sequence + + @property + def active_routers(self): + return self._active_routers + + @property + def sed_buffer_size(self): + return self._sed_buffer_size + + @property + def sed_datagram_count(self): + return self._sed_datagram_count + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.pp == other.pp and \ + self.link_quality_3 == other.link_quality_3 and \ + self.link_quality_2 == other.link_quality_2 and \ + self.link_quality_1 == other.link_quality_1 and \ + self.leader_cost == other.leader_cost and \ + self.id_sequence == other.id_sequence and \ + self.active_routers == other.active_routers and \ + self.sed_buffer_size == other.sed_buffer_size and \ + self.sed_datagram_count == other.sed_datagram_count + + def __repr__(self): + return "Connectivity(pp={}, \ + link_quality_3={}, \ + link_quality_2={}, \ + link_quality_1={}, \ + leader_cost={}, \ + id_sequence={}, \ + active_routers={}, \ + sed_buffer_size={}, \ + sed_datagram_count={})".format( + self.pp, + self.link_quality_3, + self.link_quality_2, + self.link_quality_1, + self.leader_cost, + self.id_sequence, + self.active_routers, + self.sed_buffer_size, + self.sed_datagram_count + ) + + +class ConnectivityFactory: + + def parse(self, data, message_info): + pp = ord(data.read(1)) & 0x03 + link_quality_3 = ord(data.read(1)) + link_quality_2 = ord(data.read(1)) + link_quality_1 = ord(data.read(1)) + leader_cost = ord(data.read(1)) + id_sequence = ord(data.read(1)) + active_routers = ord(data.read(1)) + + sed_data = io.BytesIO(data.read(3)) + + if len(sed_data.getvalue()) > 0: + sed_buffer_size = struct.unpack(">H", sed_data.read(2))[0] + sed_datagram_count = ord(sed_data.read(1)) + else: + sed_buffer_size = None + sed_datagram_count = None + + return Connectivity(pp, + link_quality_3, + link_quality_2, + link_quality_1, + leader_cost, + id_sequence, + active_routers, + sed_buffer_size, + sed_datagram_count) + + +class LinkMargin(object): + + def __init__(self, link_margin): + self._link_margin = link_margin + + @property + def link_margin(self): + return self._link_margin + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.link_margin == other.link_margin + + def __repr__(self): + return "LinkMargin(link_margin={})".format(self.link_margin) + + +class LinkMarginFactory: + + def parse(self, data, message_info): + link_margin = ord(data.read(1)) + return LinkMargin(link_margin) + + +class Status(object): + + def __init__(self, status): + self._status = status + + @property + def status(self): + return self._status + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.status == other.status + + def __repr__(self): + return "Status(status={})".format(self.status) + + +class StatusFactory: + + def parse(self, data, message_info): + status = ord(data.read(1)) + return Status(status) + + +class Version(object): + + def __init__(self, version): + self._version = version + + @property + def version(self): + return self._version + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.version == other.version + + def __repr__(self): + return "Version(version={})".format(self.version) + + +class VersionFactory: + + def parse(self, data, message_info): + version = struct.unpack(">H", data.read(2))[0] + return Version(version) + + +class AddressFull(object): + + def __init__(self, ipv6_address): + self._ipv6_address = ipv6_address + + @property + def ipv6_address(self): + return self._ipv6_address + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.ipv6_address == other.ipv6_address + + def __repr__(self): + return "AddressFull(ipv6_address=b'{}'')".format(hexlify(self.ipv6_address)) + + +class AddressFullFactory: + + def parse(self, data, message_info): + data.read(1) # first byte is ignored + ipv6_address = data.read(16) + return AddressFull(ipv6_address) + + +class AddressCompressed(object): + + def __init__(self, cid, iid): + self._cid = cid + self._iid = iid + + @property + def cid(self): + return self._cid + + @property + def iid(self): + return self._iid + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.cid == other.cid and self.iid == other.iid + + def __repr__(self): + return "AddressCompressed(cid={}, iid=b'{}'')".format(self.cid, hexlify(self.iid)) + + +class AddressCompressedFactory: + + def parse(self, data, message_info): + cid = (ord(data.read(1)) & 0x0F) + iid = bytearray(data.read(8)) + return AddressCompressed(cid, iid) + + +class AddressRegistration(object): + + def __init__(self, addresses): + self._addresses = addresses + + @property + def addresses(self): + return self._addresses + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.addresses == other.addresses + + def __repr__(self): + addresses_str = ", ".join(["{}".format(address) for address in self.addresses]) + return "AddressRegistration(addresses=[{}])".format(addresses_str) + + +class AddressRegistrationFactory: + + def __init__(self, addr_compressed_factory, addr_full_factory): + self._addr_compressed_factory = addr_compressed_factory + self._addr_full_factory = addr_full_factory + + def parse(self, data, message_info): + addresses = [] + + while data.tell() < len(data.getvalue()): + compressed = (ord(data.read(1)) >> 7) & 0x01 + data.seek(-1, io.SEEK_CUR) + + if compressed: + addresses.append(self._addr_compressed_factory.parse(data, message_info)) + else: + addresses.append(self._addr_full_factory.parse(data, message_info)) + + return AddressRegistration(addresses) + + +class Channel(object): + + def __init__(self, channel_page, channel): + self._channel_page = channel_page + self._channel = channel + + @property + def channel_page(self): + return self._channel_page + + @property + def channel(self): + return self._channel + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.channel_page == other.channel_page and \ + self.channel == other.channel + + def __repr__(self): + return "Channel(channel_page={}, channel={})".format(self.channel_page, channel) + + +class ChannelFactory: + + def parse(self, data, message_info): + channel_page = ord(data.read(1)) + channel = struct.unpack(">H", data.read(2))[0] + return Channel(channel_page, channel) + + +class PanId: + + def __init__(self, pan_id): + self._pan_id = pan_id + + @property + def pan_id(self): + return self._pan_id + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.pan_id == other.pan_id + + def __repr__(self): + return "PanId(pan_id={})".format(self.pan_id) + + +class PanIdFactory: + + def parse(self, data, message_info): + pan_id = struct.unpack(">H", data.read(2))[0] + return PanId(pan_id) + + +class ActiveTimestamp(object): + + def __init__(self, timestamp_seconds, timestamp_ticks, u): + self._timestamp_seconds = timestamp_seconds + self._timestamp_ticks = timestamp_ticks + self._u = u + + @property + def timestamp_seconds(self): + return self._timestamp_seconds + + @property + def timestamp_ticks(self): + return self._timestamp_ticks + + @property + def u(self): + return self._u + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.timestamp_seconds == other.timestamp_seconds and \ + self.timestamp_ticks == other.timestamp_ticks and \ + self.u == other.u + + def __repr__(self): + return "ActiveTimestamp(timestamp_seconds={}, timestamp_ticks={}, u={})".format( + self.timestamp_seconds, self.timestamp_ticks, self.u) + + +class ActiveTimestampFactory: + + def parse(self, data, message_info): + seconds = bytearray([0x00, 0x00]) + bytearray(data.read(6)) + ticks = struct.unpack(">H", data.read(2))[0] + + timestamp_seconds = struct.unpack(">Q", bytes(seconds))[0] + timestamp_ticks = (ticks >> 1) + u = ticks & 0x01 + return ActiveTimestamp(timestamp_seconds, timestamp_ticks, u) + + +class PendingTimestamp(object): + + def __init__(self, timestamp_seconds, timestamp_ticks, u): + self._timestamp_seconds = timestamp_seconds + self._timestamp_ticks = timestamp_ticks + self._u = u + + @property + def timestamp_seconds(self): + return self._timestamp_seconds + + @property + def timestamp_ticks(self): + return self._timestamp_ticks + + @property + def u(self): + return self._u + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.timestamp_seconds == other.timestamp_seconds and \ + self.timestamp_ticks == other.timestamp_ticks and \ + self.u == other.u + + def __repr__(self): + return "PendingTimestamp(timestamp_seconds={}, timestamp_ticks={}, u={})".format( + self.timestamp_seconds, self.timestamp_ticks, self.u) + + +class PendingTimestampFactory: + + def parse(self, data, message_info): + seconds = bytearray([0x00, 0x00]) + bytearray(data.read(6)) + ticks = struct.unpack(">H", data.read(2))[0] + + timestamp_seconds = struct.unpack(">Q", bytes(seconds))[0] + timestamp_ticks = (ticks >> 1) + u = ticks & 0x01 + return PendingTimestamp(timestamp_seconds, timestamp_ticks, u) + + +class ActiveOperationalDataset: + # TODO: Not implemented yet + + def __init__(self): + print("ActiveOperationalDataset is not implemented yet.") + + +class ActiveOperationalDatasetFactory: + + def parse(self, data, message_info): + return ActiveOperationalDataset() + + +class PendingOperationalDataset: + # TODO: Not implemented yet + + def __init__(self): + print("PendingOperationalDataset is not implemented yet.") + + +class PendingOperationalDatasetFactory: + + def parse(self, data, message_info): + return PendingOperationalDataset() + + +class ThreadDiscovery: + # TODO: Not implemented yet + + def __init__(self): + print("ThreadDiscovery is not implemented yet.") + + +class ThreadDiscoveryFactory: + + def parse(self, data, message_info): + return ThreadDiscovery() + + +class MleCommand(object): + + def __init__(self, _type, tlvs): + self._type = _type + self._tlvs = tlvs + + @property + def type(self): + return self._type + + @property + def tlvs(self): + return self._tlvs + + def __repr__(self): + tlvs_str = ", ".join(["{}".format(tlv) for tlv in self.tlvs]) + return "MleCommand(type={}, tlvs=[{}])".format(self.type, tlvs_str) + + +class MleCommandFactory: + + _MARKER_EXTENDED_LENGTH = 0xff + + def __init__(self, tlvs_factories): + self._tlvs_factories = tlvs_factories + + def _get_length(self, data): + length = ord(data.read(1)) + + if length == self._MARKER_EXTENDED_LENGTH: + length = struct.unpack(">H", data.read(2))[0] + + return length + + def _get_tlv_factory(self, _type): + try: + return self._tlvs_factories[_type] + except KeyError: + raise KeyError("Could not find TLV factory. Unsupported TLV type: {}".format(_type)) + + def _parse_tlv(self, data, message_info): + _type = ord(data.read(1)) + length = self._get_length(data) + value = data.read(length) + + factory = self._get_tlv_factory(_type) + + return factory.parse(io.BytesIO(value), message_info) + + def parse(self, data, message_info): + cmd_type = ord(data.read(1)) + tlvs = [] + + while data.tell() < len(data.getvalue()): + tlv = self._parse_tlv(data, message_info) + tlvs.append(tlv) + + return MleCommand(cmd_type, tlvs) + + +class MleMessage(object): + + def __init__(self, command): + self._command = command + + @property + def command(self): + return self._command + + def __repr__(self): + return "MleMessage(command={})".format(self.command) + + +class MleMessageSecured(MleMessage): + + def __init__(self, aux_sec_hdr, command, mic): + super(MleMessageSecured, self).__init__(command) + self._aux_sec_hdr = aux_sec_hdr + self._mic = mic + + @property + def aux_sec_hdr(self): + return self._aux_sec_hdr + + @property + def mic(self): + return self._mic + + def __repr__(self): + return "MleMessageSecured(aux_sec_hdr={}, command={}, mic=\"{}\")".format( + self.aux_sec_hdr, self.command, hexlify(self.mic)) + + +class MleMessageFactory: + + def __init__(self, aux_sec_hdr_factory, mle_command_factory, crypto_engine): + self._aux_sec_hdr_factory = aux_sec_hdr_factory + self._mle_command_factory = mle_command_factory + self._crypto_engine = crypto_engine + + def _create_mle_secured_message(self, data, message_info): + aux_sec_hdr = self._aux_sec_hdr_factory.parse(data, message_info) + + enc_data_length = len(data.getvalue()) + + enc_data = bytearray(data.read(enc_data_length - data.tell() - self._crypto_engine.mic_length)) + mic = bytearray(data.read()) + + dec_data = self._crypto_engine.decrypt(enc_data, mic, message_info) + + command = self._mle_command_factory.parse(io.BytesIO(dec_data), message_info) + + return MleMessageSecured(aux_sec_hdr, command, mic) + + def _create_mle_message(self, data, message_info): + command = self._mle_command_factory.parse(data, message_info) + + return MleMessage(command) + + def parse(self, data, message_info): + security_indicator = ord(data.read(1)) + + if security_indicator == 0: + return self._create_mle_secured_message(data, message_info) + + elif security_indicator == 255: + return self._create_mle_message(data, message_info) + + else: + raise RuntimeError( + "Could not create MLE message. Unknown security indicator value: {}".format(security_indicator)) diff --git a/tests/scripts/thread-cert/net_crypto.py b/tests/scripts/thread-cert/net_crypto.py new file mode 100644 index 000000000..b9c552753 --- /dev/null +++ b/tests/scripts/thread-cert/net_crypto.py @@ -0,0 +1,359 @@ +#!/usr/bin/python +# +# Copyright (c) 2016, 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 hmac +import hashlib +import struct + +from binascii import hexlify + +from Crypto.Cipher import AES + + +class CryptoEngine: + + """ Class responsible for encryption and decryption of data. """ + + def __init__(self, crypto_material_creator): + """ + Args: + master_key (bytearray) + + """ + self._crypto_material_creator = crypto_material_creator + + @property + def mic_length(self): + return self._crypto_material_creator.mic_length + + def encrypt(self, data, message_info): + """ Encrypt message. + + Args: + data (bytearray) + message_info (MessageInfo) + + Returns: + tuple: Encrypted message (bytearray), MIC (bytearray) + + """ + key, nonce, auth_data = self._crypto_material_creator.create_key_and_nonce_and_authenticated_data(message_info) + + cipher = AES.new(key, AES.MODE_CCM, nonce, mac_len=self.mic_length) + cipher.update(auth_data) + + return cipher.encrypt_and_digest(bytes(data)) + + def decrypt(self, enc_data, mic, message_info): + """ Decrypt MLE message. + + Args: + enc_data (bytearray) + mic (bytearray) + message_info (MessageInfo) + + Returns: + bytearray: Decrypted message. + + """ + key, nonce, auth_data = self._crypto_material_creator.create_key_and_nonce_and_authenticated_data(message_info) + + cipher = AES.new(key, AES.MODE_CCM, nonce, mac_len=self.mic_length) + cipher.update(auth_data) + + dec_data = cipher.decrypt_and_verify(bytes(enc_data), bytes(mic)) + return bytearray(dec_data) + + +class CryptoMaterialCreator(object): + + _salt = b'Thread' + + def __init__(self, master_key): + """ + Args: + master_key (bytearray) + + """ + self.master_key = master_key + + def _generate_keys(self, sequence_counter): + """ Generate MLE and MAC keys. + + Read more: 7.1.4. Key Generation - Thread v1.1 Specification Final + + Args: + sequence_counter (int) + + Returns: + tuple: MLE and MAC as bytes + + """ + k = self.master_key + s = struct.pack(">L", sequence_counter) + self._salt + d = hmac.new(k, s, digestmod=hashlib.sha256).digest() + + mle = d[:16] + mac = d[16:] + return mle, mac + + def create_key_and_nonce_and_authenticated_data(self, message_info): + raise NotImplementedError + + @property + def mic_length(self): + raise NotImplementedError + + +class MacCryptoMaterialCreator(CryptoMaterialCreator): + + def __init__(self, master_key): + """ + Args: + master_key (bytearray) + + """ + super(MacCryptoMaterialCreator, self).__init__(master_key) + + def _create_nonce(self, eui64, frame_counter, security_level): + """ Create CCM Nonce required by AES-128 CCM for encryption and decryption. + + Read more: 7.6.3.2 CCM Nonce - Std 802.15.4-2006 + + Args: + eui64 (bytes) + frame_counter (int) + security_level (int) + + Returns: + bytes: created Nonce + + """ + return bytes(eui64 + struct.pack(">LB", frame_counter, security_level)) + + def _create_authenticated_data(self, mhr, auxiliary_security_header, nonpayload_fields): + """ Create Authenticated Data + + Read more: 7.6.3.3 CCM prerequisites - Std 802.15.4-2006 + + Args: + mhr (bytes) + auxiliary_security_header (bytes) + nonpayload_fields (bytes) + + Returns: + bytes: Authenticated Data + + """ + return bytes(mhr + auxiliary_security_header + nonpayload_fields) + + def create_key_and_nonce_and_authenticated_data(self, message_info): + _, mac_key = self._generate_keys(message_info.aux_sec_hdr.sequence_counter) + + nonce = self._create_nonce(message_info.source_mac_address, + message_info.aux_sec_hdr.frame_counter, + message_info.aux_sec_hdr.security_level) + + auth_data = self._create_authenticated_data(message_info.mhr_bytes, + message_info.aux_sec_hdr_bytes, + message_info.nonpayload_fields) + + return mac_key, nonce, auth_data + + @property + def mic_length(self): + return 4 + + +class MleCryptoMaterialCreator(CryptoMaterialCreator): + + def __init__(self, master_key): + """ + Args: + master_key (bytearray) + + """ + super(MleCryptoMaterialCreator, self).__init__(master_key) + + def _create_nonce(self, source_eui64, frame_counter, security_level): + """ Create CCM Nonce required by AES-128 CCM for encryption and decryption. + + Read more: 7.6.3.2 CCM Nonce - Std 802.15.4-2006 + + Args: + eui64 (bytearray) + frame_counter (int) + security_level (int) + + Returns: + bytes: created Nonce + + """ + return bytes(source_eui64[:8] + struct.pack(">LB", frame_counter, security_level)) + + def _create_authenticated_data(self, source_address, destination_address, auxiliary_security_header): + """ Create Authenticated Data + + Read more: 4.8 - Thread v1.0 Specification + + Args: + source_address (ip_address) + destination_address (ip_address) + auxiliary_security_header (bytearray) + + Returns: + bytes: Authenticated Data + + """ + return bytes(source_address.packed + destination_address.packed + auxiliary_security_header) + + def create_key_and_nonce_and_authenticated_data(self, message_info): + mle_key, _ = self._generate_keys(message_info.aux_sec_hdr.sequence_counter) + + nonce = self._create_nonce(message_info.source_mac_address.mac_address, + message_info.aux_sec_hdr.frame_counter, + message_info.aux_sec_hdr.security_level) + + auth_data = self._create_authenticated_data(message_info.source_ipv6, + message_info.destination_ipv6, + message_info.aux_sec_hdr_bytes) + + return mle_key, nonce, auth_data + + @property + def mic_length(self): + return 4 + + +class AuxiliarySecurityHeader: + + def __init__(self, key_id_mode, security_level, frame_counter, key_id, big_endian=True): + """ + Args: + key_id_mode (int) + security_level (int) + frame_counter (int) + key_id (bytearray) + """ + self._key_id_mode = key_id_mode + self._security_level = security_level + self._frame_counter = frame_counter + self._key_id = key_id + self._big_endian = big_endian + + @property + def sequence_counter(self): + """ Compute or extract sequence counter based on currently set Key Index Mode. """ + + if self.key_id_mode == 1: + # Try to guess valid Key Sequence Counter based on Key Index. This one should work for now. + return self.key_index - 1 + elif self.key_id_mode == 2: + # In this mode sequence counter is stored on the first four bytes of Key ID. + key_source = self.key_id[:4] + format = ">I" if self._big_endian else "B", self.key_id[-1:])[0] + + @property + def key_id_mode(self): + return self._key_id_mode + + @property + def security_level(self): + return self._security_level + + @property + def frame_counter(self): + return self._frame_counter + + @property + def key_id(self): + return self._key_id + + def __repr__(self): + return "AuxiliarySecurityHeader(key_id_mode={}, security_level={}, frame_counter={}, key_id={})".format( + self.key_id_mode, self.security_level, self.frame_counter, hexlify(self.key_id)) + + +class AuxiliarySecurityHeaderFactory: + + _SECURITY_CONTROL_LENGTH = 1 + _FRAME_COUNTER_LENGTH = 4 + + _KEY_ID_LENGTH_KEY_ID_0 = 0 + _KEY_ID_LENGTH_KEY_ID_1 = 1 + _KEY_ID_LENGTH_KEY_ID_2 = 5 + _KEY_ID_LENGTH_KEY_ID_3 = 9 + + _key_id_lengths = { + 0: _KEY_ID_LENGTH_KEY_ID_0, + 1: _KEY_ID_LENGTH_KEY_ID_1, + 2: _KEY_ID_LENGTH_KEY_ID_2, + 3: _KEY_ID_LENGTH_KEY_ID_3 + } + + def _parse_security_control(self, security_control_byte): + security_level = (security_control_byte & 0x07) + key_id_mode = (security_control_byte >> 3) & 0x03 + + return security_level, key_id_mode + + def _parse_frame_counter(self, frame_counter_bytes): + return struct.unpack("> 1) & 0x7f + + length = ord(data.read(1)) + value = data.read(length) + + factory = self._get_factory(_type) + + message_info.stable = stable + tlv = factory.parse(io.BytesIO(value), message_info) + + sub_tlvs.append(tlv) + + return sub_tlvs + + +class Route(object): + + def __init__(self, border_router_16, prf): + self._border_router_16 = border_router_16 + self._prf = prf + + @property + def border_router_16(self): + return self._border_router_16 + + @property + def prf(self): + return self._prf + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.border_router_16 == other.border_router_16 and self.prf == other.prf + + def __repr__(self): + return "Route(border_router_16={}, prf={})".format(self.border_router_16, self.prf) + + +class RouteFactory: + + def parse(self, data, message_info): + border_router_16 = struct.unpack(">H", data.read(2))[0] + + data_byte = ord(data.read(1)) + prf = (data_byte >> 6) & 0x03 + + return Route(border_router_16, prf) + + +class RoutesFactory: + + def __init__(self, route_factory): + self._route_factory = route_factory + + def parse(self, data, message_info): + routes = [] + + while data.tell() < len(data.getvalue()): + route = self._route_factory.parse(data, message_info) + + routes.append(route) + + return routes + + +class HasRoute(NetworkData): + + def __init__(self, routes, stable): + super(HasRoute, self).__init__(stable) + self._routes = routes + + @property + def routes(self): + return self._routes + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.routes == other.routes + + def __repr__(self): + routes_str = ", ".join(["{}".format(route) for route in self.routes]) + return "HasRoute(stable={}, routes=[{}])".format(self.stable, routes_str) + + +class HasRouteFactory: + + def __init__(self, routes_factory): + self._routes_factory = routes_factory + + def parse(self, data, message_info): + routes = self._routes_factory.parse(data, message_info) + + return HasRoute(routes, message_info.stable) + + +class Prefix(NetworkData): + + def __init__(self, domain_id, prefix_length, prefix, sub_tlvs, stable): + super(Prefix, self).__init__(stable) + self._domain_id = domain_id + self._prefix_length = prefix_length + self._prefix = prefix + self._sub_tlvs = sub_tlvs + + @property + def domain_id(self): + return self._domain_id + + @property + def prefix_length(self): + return self._prefix_length + + @property + def prefix(self): + return self._prefix + + @property + def sub_tlvs(self): + return self._sub_tlvs + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.domain_id == other.domain_id and \ + self.prefix_length == other.prefix_length and \ + self.prefix == other.prefix and \ + self.sub_tlvs == other.sub_tlvs + + def __repr__(self): + sub_tlvs_str = ", ".join(["{}".format(tlv) for tlv in self.sub_tlvs]) + return "Prefix(stable={}, domain_id={}, prefix_length={}, prefix={}, sub_tlvs=[{}])".format( + self.stable, self.domain_id, self.prefix_length, hexlify(self.prefix), sub_tlvs_str) + + +class PrefixSubTlvsFactory(SubTlvsFactory): + + def __init__(self, sub_tlvs_factories): + super(PrefixSubTlvsFactory, self).__init__(sub_tlvs_factories) + + +class PrefixFactory: + + def __init__(self, sub_tlvs_factory): + self._sub_tlvs_factory = sub_tlvs_factory + + def _bits_to_bytes(self, bits): + return int(math.ceil(bits / 8)) + + def parse(self, data, message_info): + domain_id = ord(data.read(1)) + + prefix_length = ord(data.read(1)) + + prefix = bytearray(data.read(self._bits_to_bytes(prefix_length))) + + sub_tlvs = self._sub_tlvs_factory.parse(io.BytesIO(data.read()), message_info) + + return Prefix(domain_id, prefix_length, prefix, sub_tlvs, message_info.stable) + + +class BorderRouter(NetworkData): + + def __init__(self, border_router_16, prf, p, s, d, c, r, o, n, stable): + super(BorderRouter, self).__init__(stable) + self._border_router_16 = border_router_16 + self._prf = prf + self._p = p + self._s = s + self._d = d + self._c = c + self._r = r + self._o = o + self._n = n + + @property + def border_router_16(self): + return self._border_router_16 + + @property + def prf(self): + return self._prf + + @property + def p(self): + return self._p + + @property + def s(self): + return self._s + + @property + def d(self): + return self._d + + @property + def c(self): + return self._c + + @property + def r(self): + return self._r + + @property + def o(self): + return self._o + + @property + def n(self): + return self._n + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.border_router_16 == other.border_router_16 and \ + self.prf == other.prf and \ + self.p == other.p and \ + self.s == other.s and \ + self.d == other.d and \ + self.c == other.c and \ + self.r == other.r and \ + self.o == other.o and \ + self.n == other.n + + def __repr__(self): + return "BorderRouter(stable={}, border_router_16={}, prf={}, p={}, s={}, d={}, c={}, r={}, o={}, n={})".format( + self.stable, self.border_router_16, self.prf, self.p, self.s, self.d, self.c, self.r, self.o, self.n) + + +class BorderRouterFactory: + + def parse(self, data, message_info): + border_router_16 = struct.unpack(">H", data.read(2))[0] + + data_byte = ord(data.read(1)) + o = (data_byte & 0x01) + r = (data_byte >> 1) & 0x01 + c = (data_byte >> 2) & 0x01 + d = (data_byte >> 3) & 0x01 + s = (data_byte >> 4) & 0x01 + p = (data_byte >> 5) & 0x01 + prf = (data_byte >> 6) & 0x03 + + data_byte = ord(data.read(1)) + n = (data_byte >> 7) & 0x01 + + return BorderRouter(border_router_16, prf, p, s, d, c, r, o, n, message_info.stable) + + +class LowpanId(NetworkData): + + def __init__(self, c, cid, context_length, stable): + super(LowpanId, self).__init__(stable) + self._c = c + self._cid = cid + self._context_length = context_length + + @property + def c(self): + return self._c + + @property + def cid(self): + return self._cid + + @property + def context_length(self): + return self._context_length + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.c == other.c and \ + self.cid == other.cid and \ + self.context_length == other.context_length + + def __repr__(self): + return "LowpanId(stable={}, c={}, cid={}, context_length={})".format( + self.stable, self.c, self.cid, self.context_length) + + +class LowpanIdFactory: + + def parse(self, data, message_info): + data_byte = ord(data.read(1)) + + cid = (data_byte & 0x0f) + c = (data_byte >> 4) & 0x01 + + context_length = ord(data.read(1)) + + return LowpanId(c, cid, context_length, message_info.stable) + + +class CommissioningData: + + def __init__(self): + # TODO: Not implemented yet + raise NotImplementedError + + +class CommissioningDataFactory: + + def __init__(self): + # TODO: Not implemented yet + raise NotImplementedError + + +class Service(NetworkData): + + def __init__(self, t, _id, enterprise_number, service_data_length, service_data, sub_tlvs, stable): + super(Service, self).__init__(stable) + self._t = t + self._id = _id + self._enterprise_number = enterprise_number + self._service_data_length = service_data_length + self._service_data = service_data + self._sub_tlvs = sub_tlvs + + @property + def t(self): + return self._t + + @property + def id(self): + return self._id + + @property + def enterprise_number(self): + return self._enterprise_number + + @property + def service_data_length(self): + return self._service_data_length + + @property + def service_data(self): + return self._service_data + + @property + def sub_tlvs(self): + return self._sub_tlvs + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.t == other.t and \ + self.id == other.id and \ + self.enterprise_number == other.enterprise_number and \ + self.service_data_length == other.service_data_length and \ + self.service_data == other.service_data and \ + self.sub_tlvs == other.sub_tlvs + + def __repr__(self): + sub_tlvs_str = ", ".format(["{}".format(tlv) for tlv in self.sub_tlvs]) + return "LowpanId(stable={}, t={}, id={}, enterprise_number={}, service_data_length={}, service_data={}, sub_tlvs=[{}])".format( + self.stable, self.t, self.id, self.enterprise_number, self.service_data_length, self.service_data, sub_tlvs_str) + + +class ServiceSubTlvsFactory(SubTlvsFactory): + + def __init__(self, sub_tlvs_factories): + super(ServiceSubTlvsFactory, self).__init__(sub_tlvs_factories) + + +class ServiceFactory: + + def __init__(self, sub_tlvs_factory): + self._sub_tlvs_factory = sub_tlvs_factory + + def parse(self, data, message_info): + data_byte = ord(data.read(1)) + t = (data_byte >> 7) & 0x01 + _id = (data_byte & 0x0f) + + enterprise_number = struct.unpack(">L", data.read(4))[0] + service_data_length = ord(data.read(1)) + service_data = data.read(service_data_length) + + sub_tlvs = self._sub_tlvs_factory.parse(io.BytesIO(data.read()), message_info) + + return Service(t, _id, enterprise_number, service_data_length, service_data, sub_tlvs, message_info.stable) + + +class Server(NetworkData): + + def __init__(self, server_16, server_data, stable): + super(Server, self).__init__(stable) + self._server_16 = server_16 + self._server_data = server_data + + @property + def server_16(self): + return self._server_16 + + @property + def server_data(self): + return self._server_data + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("Could not compare {} and {}".format(type(self), type(other))) + + return self.server_16 == other.server_16 and \ + self.server_data == other.server_data + + def __repr__(self): + return "LowpanId(stable={}, server_16={}, server_data=\"{}\")".format( + self.stable, self.server_16, hexlify(self.server_data)) + + +class ServerFactory: + + def parse(self, data, message_info): + server_16 = struct.unpack(">H", data.read(2))[0] + server_data = bytearray(data.read()) + + return Server(server_16, server_data, message_info.stable) + + +class NetworkDataTlvsFactory(SubTlvsFactory): + + def __init__(self, sub_tlvs_factories): + super(NetworkDataTlvsFactory, self).__init__(sub_tlvs_factories) diff --git a/tests/scripts/thread-cert/sniffer.py b/tests/scripts/thread-cert/sniffer.py new file mode 100644 index 000000000..571bdb833 --- /dev/null +++ b/tests/scripts/thread-cert/sniffer.py @@ -0,0 +1,163 @@ +#!/usr/bin/python +# +# Copyright (c) 2016, 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 collections +import io +import logging +import os +import Queue +import select +import socket +import threading + +import message + + +class Sniffer: + + """ Class representing the Sniffing node, whose main task is listening and logging message exchange performed by other nodes. """ + + logger = logging.getLogger("sniffer.Sniffer") + + POLL_TIMEOUT = 0.11 + + RECV_BUFFER_SIZE = 4096 + + BASE_PORT = 9000 + + WELLKNOWN_NODE_ID = 34 + + PORT_OFFSET = int(os.getenv('PORT_OFFSET', "0")) + + def __init__(self, nodeid, message_factory): + """ + Args: + nodeid (int): Node identifier + message_factory (MessageFactory): Class producing messages from data bytes. + """ + + self.nodeid = nodeid + self._message_factory = message_factory + + self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + if self._socket is None: + raise RuntimeError("Could not create socket.") + + self._socket.bind(self.address) + + self._thread = None + self._thread_alive_event = threading.Event() + + self._poll = select.poll() + + self._buckets = collections.defaultdict(Queue.Queue) + + def _nodeid_to_address(self, nodeid, ip_address="localhost"): + return "", self.BASE_PORT + (self.PORT_OFFSET * self.WELLKNOWN_NODE_ID) + nodeid + + def _address_to_nodeid(self, address): + ip_address, port = address + return (port - self.BASE_PORT - (self.PORT_OFFSET * self.WELLKNOWN_NODE_ID)) + + def _recv(self, fd): + """ Receive data from socket with passed file descriptor. """ + + data, address = socket.fromfd(fd, socket.AF_INET, socket.SOCK_DGRAM).recvfrom(self.RECV_BUFFER_SIZE) + + msg = self._message_factory.create(io.BytesIO(data)) + + if msg is None: + self.logger.debug("Received 6LowPAN fragment.") + return + + nodeid = self._address_to_nodeid(address) + + self._buckets[nodeid].put(msg) + + @property + def address(self): + """ Sniffer address. """ + + return self._nodeid_to_address(self.nodeid) + + def _run(self): + """ Receive thread main loop. """ + + while self._thread_alive_event.is_set(): + reported_events = self._poll.poll(self.POLL_TIMEOUT) + + for fd_event_pair in reported_events: + fd, event = fd_event_pair + + if event & select.POLLIN or event & select.POLLPRI: + self._recv(fd) + + elif event & select.POLLERR: + self.logger.error("Error condition of some sort") + self._thread_alive_event.clear() + break + + elif event & select.POLLNVAL: + self.logger.error("Invalid request: descriptor not open") + self._thread_alive_event.clear() + break + + def start(self): + """ Start sniffing. """ + + self._poll.register(self._socket, select.POLLIN | select.POLLPRI | select.POLLERR | select.POLLNVAL) + + self._thread = threading.Thread(target=self._run) + self._thread.daemon = True + + self._thread_alive_event.set() + self._thread.start() + + def stop(self): + """ Stop sniffing. """ + + self._poll.unregister(self._socket) + + self._thread_alive_event.clear() + self._thread.join() + self._thread = None + + def get_messages_sent_by(self, nodeid): + """ Get sniffed messages. + + Note! This method flushes the message queue so calling this method again will return only the newly logged messages. + + """ + bucket = self._buckets[nodeid] + messages = [] + + while not bucket.empty(): + messages.append(bucket.get_nowait()) + + return message.MessagesSet(messages) diff --git a/tests/scripts/thread-cert/test_common.py b/tests/scripts/thread-cert/test_common.py new file mode 100644 index 000000000..4114f230e --- /dev/null +++ b/tests/scripts/thread-cert/test_common.py @@ -0,0 +1,170 @@ +#!/usr/bin/python +# +# Copyright (c) 2016, 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 random +import struct +import unittest +import ipaddress + +import common + + +def any_eui64(): + return bytearray([random.getrandbits(8) for _ in xrange(8)]) + + +def any_rloc16_int(): + return random.getrandbits(16) + + +def any_rloc16_bytearray(): + return bytearray([random.getrandbits(8) for _ in xrange(2)]) + + +def any_ipv6_address(): + return bytearray([random.getrandbits(8) for _ in xrange(16)]) + + +class TestMessageInfo(unittest.TestCase): + + def test_should_return_source_ipv6_value_when_source_ipv6_property_is_called(self): + # GIVEN + source_ipv6 = any_ipv6_address() + + message_info = common.MessageInfo() + message_info.source_ipv6 = source_ipv6 + + # WHEN + actual_source_ipv6 = message_info.source_ipv6 + + # THEN + self.assertEqual(ipaddress.ip_address(bytes(source_ipv6)), actual_source_ipv6) + + def test_should_return_destination_ipv6_value_when_destination_ipv6_property_is_called(self): + # GIVEN + destination_ipv6 = any_ipv6_address() + + message_info = common.MessageInfo() + message_info.destination_ipv6 = destination_ipv6 + + # WHEN + actual_destination_ipv6 = message_info.destination_ipv6 + + # THEN + self.assertEqual(ipaddress.ip_address(bytes(destination_ipv6)), actual_destination_ipv6) + + def test_should_return_source_eui64_value_when_source_eui64_property_is_called(self): + # GIVEN + source_mac_address = any_eui64() + + message_info = common.MessageInfo() + message_info.source_mac_address = source_mac_address + + # WHEN + actual_source_mac_address = message_info.source_mac_address + + # THEN + self.assertEqual(source_mac_address, actual_source_mac_address) + + def test_should_return_destination_eui64_value_when_destination_eui64_property_is_called(self): + # GIVEN + destination_mac_address = any_eui64() + + message_info = common.MessageInfo() + message_info.destination_mac_address = destination_mac_address + + # WHEN + actual_destination_mac_address = message_info.destination_mac_address + + # THEN + self.assertEqual(destination_mac_address, actual_destination_mac_address) + + +class TestMacAddress(unittest.TestCase): + + def test_should_create_MacAddress_from_eui64_when_from_eui64_classmethod_is_called(self): + # GIVEN + eui64 = any_eui64() + + # WHEN + mac_address = common.MacAddress.from_eui64(eui64) + + # THEN + self.assertEqual(common.MacAddress.LONG, mac_address.type) + self.assertEqual(eui64, mac_address.mac_address) + + def test_should_create_MacAddress_from_rloc16_int_when_from_rloc16_classmethod_is_called(self): + # GIVEN + rloc16 = any_rloc16_int() + + # WHEN + mac_address = common.MacAddress.from_rloc16(rloc16) + + # THEN + self.assertEqual(common.MacAddress.SHORT, mac_address.type) + self.assertEqual(struct.pack(">H", rloc16), mac_address.mac_address) + + def test_should_create_MacAddress_from_rloc16_bytearray_when_from_rloc16_classmethod_is_called(self): + # GIVEN + rloc16 = any_rloc16_bytearray() + + # WHEN + mac_address = common.MacAddress.from_rloc16(rloc16) + + # THEN + self.assertEqual(common.MacAddress.SHORT, mac_address.type) + self.assertEqual(rloc16, mac_address.mac_address) + + def test_should_convert_short_MacAddress_to_iid_when_convert_method_is_called(self): + # GIVEN + rloc16 = any_rloc16_bytearray() + + mac_address = common.MacAddress.from_rloc16(rloc16) + + # WHEN + iid = mac_address.convert_to_iid() + + # THEN + self.assertEqual(bytearray([0x00, 0x00, 0x00, 0xff, 0xfe, 0x00]) + rloc16, iid) + + def test_should_convert_short_MacAddress_to_iid_when_convert_method_is_called(self): + # GIVEN + eui64 = any_eui64() + + mac_address = common.MacAddress.from_eui64(eui64) + + # WHEN + iid = mac_address.convert_to_iid() + + # THEN + self.assertEqual(bytearray([eui64[0] ^ 0x02]) + eui64[1:], iid) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/scripts/thread-cert/test_crypto.py b/tests/scripts/thread-cert/test_crypto.py new file mode 100644 index 000000000..e4bf38188 --- /dev/null +++ b/tests/scripts/thread-cert/test_crypto.py @@ -0,0 +1,431 @@ +#!/usr/bin/python +# +# Copyright (c) 2016, 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 io +import random +import struct +import unittest +import ipaddress + +import common +import net_crypto +import mle + +master_key = bytearray([0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]) + + +def convert_aux_sec_hdr_to_bytearray(aux_sec_hdr): + data = bytearray([aux_sec_hdr.security_level | ((aux_sec_hdr.key_id_mode & 0x03) << 3)]) + data += struct.pack("L", frame_counter), nonce_bytes.read(4)) + self.assertEqual(security_level, ord(nonce_bytes.read(1))) + + def test_should_create_authenticated_data_when_create_authenticated_data_method_is_called(self): + """ + Only Key id mode 2. + Length of the Auxiliary Security Header is constantly equal 10. + """ + + # GIVEN + source_address = any_ip_address() + destination_address = any_ip_address() + auxiliary_security_header_bytes = convert_aux_sec_hdr_to_bytearray(any_auxiliary_security_header()) + + creator = net_crypto.MleCryptoMaterialCreator(master_key) + + # WHEN + authenticated_data = creator._create_authenticated_data( + source_address, destination_address, auxiliary_security_header_bytes) + + # THEN + authenticated_data_bytes = io.BytesIO(authenticated_data) + + self.assertEqual(source_address.packed, authenticated_data_bytes.read(16)) + self.assertEqual(destination_address.packed, authenticated_data_bytes.read(16)) + self.assertEqual(auxiliary_security_header_bytes, authenticated_data_bytes.read(10)) + + def test_should_create_key_and_nonce_and_authenticated_data_when_create_key_and_nonce_and_authenticated_data_is_called(self): + # GIVEN + message_info = common.MessageInfo() + message_info.source_mac_address = common.MacAddress.from_eui64(any_eui64()) + + message_info.source_ipv6 = any_ip_address() + message_info.destination_ipv6 = any_ip_address() + + message_info.aux_sec_hdr = any_auxiliary_security_header() + message_info.aux_sec_hdr_bytes = convert_aux_sec_hdr_to_bytearray(message_info.aux_sec_hdr) + + creator = net_crypto.MleCryptoMaterialCreator(master_key) + + # WHEN + key, nonce, auth_data = creator.create_key_and_nonce_and_authenticated_data(message_info) + + # THEN + self.assertEqual(message_info.source_mac_address.mac_address + + struct.pack(">LB", + message_info.aux_sec_hdr.frame_counter, + message_info.aux_sec_hdr.security_level), nonce) + + self.assertEqual(message_info.source_ipv6.packed + + message_info.destination_ipv6.packed + + message_info.aux_sec_hdr_bytes, auth_data) + + +class TestAuxiliarySecurityHeader(unittest.TestCase): + + def test_should_return_key_id_mode_value_when_key_id_mode_property_is_called(self): + # GIVEN + key_id_mode = any_key_id_mode() + + aux_sec_hdr_obj = net_crypto.AuxiliarySecurityHeader( + key_id_mode, any_security_level(), any_frame_counter(), any_key_id(key_id_mode)) + + # WHEN + actual_key_id_mode = aux_sec_hdr_obj.key_id_mode + + # THEN + self.assertEqual(key_id_mode, actual_key_id_mode) + + def test_should_return_security_level_value_when_security_level_property_is_called(self): + # GIVEN + security_level = any_security_level() + key_id_mode = any_key_id_mode() + + aux_sec_hdr_obj = net_crypto.AuxiliarySecurityHeader( + key_id_mode, security_level, any_frame_counter(), any_key_id(key_id_mode)) + + # WHEN + actual_security_level = aux_sec_hdr_obj.security_level + + # THEN + self.assertEqual(security_level, actual_security_level) + + def test_should_return_frame_counter_value_when_frame_counter_property_is_called(self): + # GIVEN + frame_counter = any_frame_counter() + key_id_mode = any_key_id_mode() + + aux_sec_hdr_obj = net_crypto.AuxiliarySecurityHeader( + key_id_mode, any_security_level(), frame_counter, any_key_id(key_id_mode)) + + # WHEN + actual_frame_counter = aux_sec_hdr_obj.frame_counter + + # THEN + self.assertEqual(frame_counter, actual_frame_counter) + + def test_should_return_key_id_value_when_key_id_property_is_called(self): + # GIVEN + key_id_mode = any_key_id_mode() + key_id = any_key_id(key_id_mode) + + aux_sec_hdr_obj = net_crypto.AuxiliarySecurityHeader( + key_id_mode, any_security_level(), any_frame_counter(), key_id) + + # WHEN + actual_key_id = aux_sec_hdr_obj.key_id + + # THEN + self.assertEqual(key_id, actual_key_id) + + def test_should_return_sequence_counter_value_when_sequence_counter_property_is_called(self): + # GIVEN + key_id_mode = 2 + key_id = any_key_id(key_id_mode) + + aux_sec_hdr_obj = net_crypto.AuxiliarySecurityHeader( + key_id_mode, any_security_level(), any_frame_counter(), key_id) + + # WHEN + actual_sequence_counter = aux_sec_hdr_obj.sequence_counter + + # THEN + self.assertEqual(struct.unpack(">I", key_id[:4])[0], actual_sequence_counter) + + +class TestAuxiliarySecurityHeaderFactory(unittest.TestCase): + + def test_should_create_AuxiliarySecurityHeader_from_bytearray_when_parse_method_is_called(self): + # GIVEN + key_id_mode = any_key_id_mode() + sec_lvl = any_security_level() + frame_counter = any_frame_counter() + key_id = any_key_id(key_id_mode) + + factory = net_crypto.AuxiliarySecurityHeaderFactory() + + data = bytearray([sec_lvl | key_id_mode << 3]) + struct.pack("> 8) & 0xFF, checksum & 0xFF]) + body + + +def any_udp_payload(src_port, dst_port, payload, checksum): + payload_len = len(payload) + 8 + return bytearray([(src_port >> 8) & 0xFF, src_port & 0xFF, + (dst_port >> 8) & 0xFF, dst_port & 0xFF, + (payload_len >> 8) & 0xFF, payload_len & 0xFF, + (checksum >> 8) & 0xFF, checksum & 0xFF]) + payload + + +def any_hop_by_hop_payload(next_header, hdr_ext_len, payload): + return bytearray([next_header, hdr_ext_len]) + payload + + +def any_body(): + length = any_uint(8) + return bytearray("".join([random.choice(string.ascii_letters + string.digits + string.hexdigits) for _ in xrange(length)])) + + +def any_payload(): + length = any_uint(8) + return bytearray("".join([random.choice(string.printable) for _ in range(length)])) + + +def any_ip_address(): + return bytearray([0xfe, 0x80]) + bytearray([0x00] * 6) + bytearray([random.getrandbits(8)] * 8) + + +def any_port(): + return any_uint(16) + + +def any_mpl_opt_type(): + return any_uint(8) + + +def any_mpl_opt_data_len(): + return any_uint(8) + + +def any_mpl_S(): + return any_uint(2) + + +def any_mpl_M(): + return any_uint(1) + + +def any_mpl_V(): + return any_uint(1) + + +def any_mpl_sequence(): + return any_uint(8) + + +def any_mpl_seed_id(S): + length = MPLOption._seed_id_length[S] + return bytearray("".join([random.choice(string.ascii_letters + string.digits + string.hexdigits) for _ in range(length)])) + + +def any_next_header(): + return any_uint(8) + + +def any_traffic_class(): + return any_uint(8) + + +def any_flow_label(): + return any_uint(20) + + +def any_hop_limit(): + return any_uint(8) + + +def any_payload_length(): + return any_uint(16) + + +def any_hdr_ext_len(): + return any_uint(3) + + +def any_length(): + return any_uint(4) + + +def any_str(length=8): + return "".join(random.choice(string.printable) for _ in range(length)) + + +def any_bytes(length=4): + return bytearray(any_str(length)) + + +def any_dict(keys_count=4): + keys = [any_str() for _ in range(keys_count)] + + d = {} + for key in keys: + d[key] = any_bytes() + + return d + + +def any_mpl_option(): + S = any_mpl_S() + M = any_mpl_M() + V = any_mpl_V() + sequence = any_mpl_sequence() + seed_id = any_mpl_seed_id(S) + + return MPLOption(S, M, V, sequence, seed_id) + + +def any_hop_by_hop_bytes_option_header(length=4): + return HopByHopOptionHeader(any_type(), length) + + +def any_hop_by_hop_bytes_value(length=2): + return HopByHopOptionBytesValue(any_bytes(length)) + + +def any_hop_by_hop_bytes_option(): + length = any_length() + return HopByHopOption(any_hop_by_hop_bytes_option_header(length), any_hop_by_hop_bytes_value(length)) + + +def any_hop_by_hop_mpl_option(): + mpl_option = any_mpl_option() + return HopByHopOption(any_hop_by_hop_bytes_option_header(len(mpl_option)), mpl_option) + + +def any_identifier(): + return any_uint(16) + + +def any_sequence_number(): + return any_uint(16) + + +def any_data(): + return any_bytes(random.randint(0, 32)) + + +def any_upper_layer_payload(data, _type): + return DummyUpperLayerProtocol(DummyHeader(), data, _type) + + +def any_extension_headers(): + return [] + + +def any_message_info(): + return common.MessageInfo() + + +class TestIPv6Header(unittest.TestCase): + + def test_should_convert_IPv6_header_to_bytes_when_to_bytes_method_is_called(self): + # GIVEN + traffic_class = any_traffic_class() + flow_label = any_flow_label() + payload_length = any_payload_length() + next_header = any_next_header() + hop_limit = any_hop_limit() + source_address = any_ip_address() + destination_address = any_ip_address() + + ipv6_header = IPv6Header(source_address, destination_address, traffic_class, flow_label, hop_limit, + payload_length, next_header) + + # WHEN + data = ipv6_header.to_bytes() + + # THEN + self.assertEqual(6, data[0] >> 4) + self.assertEqual(traffic_class, ((data[0] << 8 | data[1]) >> 4) & 0xFF) + self.assertEqual(flow_label, ((data[1] & 0x0F) << 16) | + (data[2] << 8) | data[3]) + self.assertEqual(payload_length, struct.unpack("!H", data[4:6])[0]) + self.assertEqual(next_header, data[6]) + self.assertEqual(hop_limit, data[7]) + self.assertEqual(source_address, data[8:24]) + self.assertEqual(destination_address, data[24:40]) + + def test_should_create_IPv6Header_when_from_bytes_classmethod_is_called(self): + # GIVEN + traffic_class = any_traffic_class() + flow_label = any_flow_label() + payload_length = any_payload_length() + next_header = any_next_header() + hop_limit = any_hop_limit() + source_address = any_ip_address() + destination_address = any_ip_address() + + data = bytearray([(6 << 4) | (traffic_class >> 4), + (traffic_class & 0xF) << 4 | (flow_label >> 16) & 0xF, + (flow_label >> 8) & 0xFF, flow_label & 0xFF, + payload_length >> 8, payload_length & 0xFF, + next_header, hop_limit]) + data += ip_address(bytes(source_address)).packed + ip_address(bytes(destination_address)).packed + + # WHEN + ipv6_header = IPv6Header.from_bytes(io.BytesIO(data)) + + # THEN + self.assertEqual(6, ipv6_header.version) + self.assertEqual(traffic_class, ipv6_header.traffic_class) + self.assertEqual(flow_label, ipv6_header.flow_label) + self.assertEqual(payload_length, ipv6_header.payload_length) + self.assertEqual(next_header, ipv6_header.next_header) + self.assertEqual(hop_limit, ipv6_header.hop_limit) + self.assertEqual(source_address, ipv6_header.source_address.packed) + self.assertEqual(destination_address, ipv6_header.destination_address.packed) + + def test_should_return_proper_header_length_when_IPv6Packet_object_is_called_in_len(self): + # GIVEN + ipv6_header = IPv6Header(any_traffic_class(), any_flow_label(), any_payload_length(), + any_next_header(), any_hop_limit(), any_ip_address(), any_ip_address()) + + # WHEN + ipv6_header_length = len(ipv6_header) + + # THEN + self.assertEqual(40, ipv6_header_length) + + +class TestUDPHeader(unittest.TestCase): + + def test_should_convert_UDP_header_to_bytes_when_to_bytes_method_is_called(self): + # GIVEN + src_port = any_port() + dst_port = any_port() + payload_length = any_payload_length() + checksum = any_checksum() + + udp_header = UDPHeader(src_port, dst_port, payload_length, checksum) + + # WHEN + data = udp_header.to_bytes() + + # THEN + self.assertEqual(src_port, struct.unpack("!H", data[0:2])[0]) + self.assertEqual(dst_port, struct.unpack("!H", data[2:4])[0]) + self.assertEqual(payload_length, struct.unpack("!H", data[4:6])[0]) + self.assertEqual(checksum, struct.unpack("!H", data[6:])[0]) + + def test_should_create_UDPHeader_when_from_bytes_classmethod_is_called(self): + # GIVEN + src_port = any_port() + dst_port = any_port() + payload_length = any_payload_length() + checksum = any_checksum() + + data = struct.pack("!H", src_port) + struct.pack("!H", dst_port) + \ + struct.pack("!H", payload_length) + struct.pack("!H", checksum) + + # WHEN + udp_header = UDPHeader.from_bytes(io.BytesIO(data)) + + # THEN + self.assertEqual(src_port, udp_header.src_port) + self.assertEqual(dst_port, udp_header.dst_port) + self.assertEqual(payload_length, udp_header.payload_length) + self.assertEqual(checksum, udp_header.checksum) + + def test_should_return_proper_header_length_when_UDPHeader_object_is_called_in_len(self): + # GIVEN + udp_header = UDPHeader(any_port(), any_port(), any_payload_length(), any_checksum()) + + # WHEN + udp_header_length = len(udp_header) + + # THEN + self.assertEqual(8, udp_header_length) + + +class TestICMPv6Header(unittest.TestCase): + + def test_should_convert_icmp_message_header_to_bytes_when_to_bytes_method_is_called(self): + # GIVEN + _type = any_type() + code = any_code() + checksum = any_checksum() + + icmpv6_header = ICMPv6Header(_type, code, checksum) + + # WHEN + data = icmpv6_header.to_bytes() + + # THEN + self.assertEqual(_type, data[0]) + self.assertEqual(code, data[1]) + self.assertEqual(checksum, struct.unpack("!H", data[2:])[0]) + + def test_should_create_ICMPv6Header_when_to_bytes_classmethod_is_called(self): + # GIVEN + _type = any_type() + code = any_code() + checksum = any_checksum() + + data = bytearray([_type, code]) + struct.pack("!H", checksum) + + # WHEN + icmpv6_header = ICMPv6Header.from_bytes(io.BytesIO(data)) + + # THEN + self.assertEqual(_type, icmpv6_header.type) + self.assertEqual(code, icmpv6_header.code) + self.assertEqual(checksum, icmpv6_header.checksum) + + def test_should_return_proper_header_length_when_ICMPv6Header_object_is_called_in_len(self): + # GIVEN + icmpv6_header = ICMPv6Header(any_type(), any_code(), any_checksum()) + + # WHEN + icmpv6_header_length = len(icmpv6_header) + + # THEN + self.assertEqual(4, icmpv6_header_length) + + +class TestIPv6Packet(unittest.TestCase): + + def test_should_build_IPv6Packet_with_ICMP_payload_from_well_know_values_when_to_bytes_method_is_called(self): + # GIVEN + + ipv6_packet = IPv6Packet(IPv6Header(source_address="fd00:1234:4555::ff:fe00:1800", + destination_address="ff03::1"), + ICMPv6(ICMPv6Header(128, 0), + ICMPv6EchoBody(0, 2, bytearray([0x80, 0x00, 0xc7, 0xbf, 0x00, 0x00, 0x00, 0x01, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41]))), + [HopByHop(options=[ + HopByHopOption(HopByHopOptionHeader(_type=0x6d), + MPLOption(S=1, M=0, V=0, sequence=2, seed_id=bytearray([0x00, 0x18]))) + ])]) + + # WHEN + ipv6_packet_bytes = ipv6_packet.to_bytes() + + # THEN + expected_ipv6_packet_bytes = bytearray([0x60, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x40, + 0xfd, 0x00, 0x12, 0x34, 0x45, 0x55, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x18, 0x00, + 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x3a, 0x00, 0x6d, 0x04, 0x40, 0x02, 0x00, 0x18, + 0x80, 0x00, 0x87, 0x12, 0x00, 0x00, 0x00, 0x02, + 0x80, 0x00, 0xc7, 0xbf, 0x00, 0x00, 0x00, 0x01, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41]) + + self.assertEqual(expected_ipv6_packet_bytes, ipv6_packet_bytes) + + def test_should_build_IPv6Packet_with_UDP_payload_from_well_know_values_when_to_bytes_method_is_called(self): + # GIVEN + ipv6_header = IPv6Header(source_address="fe80::1", + destination_address="ff02::2", + hop_limit=255) + + udp_dgram = UDPDatagram(UDPHeader(src_port=19788, dst_port=19788), + UDPBytesPayload(bytearray([0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x09, 0x01, 0x01, 0x0b, 0x03, + 0x04, 0xc6, 0x69, 0x73, 0x51, 0x0e, 0x01, 0x80, + 0x12, 0x02, 0x00, 0x01, 0xde, 0xad, 0xbe, 0xef]))) + + ipv6_packet = IPv6Packet(ipv6_header, udp_dgram) + + # WHEN + ipv6_packet_bytes = ipv6_packet.to_bytes() + + # THEN + expected_ipv6_packet_bytes = bytearray([0x60, 0x00, 0x00, 0x00, 0x00, 0x28, 0x11, 0xff, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x4d, 0x4c, 0x4d, 0x4c, 0x00, 0x28, 0xe9, 0xf4, + 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x09, 0x01, 0x01, 0x0b, 0x03, + 0x04, 0xc6, 0x69, 0x73, 0x51, 0x0e, 0x01, 0x80, + 0x12, 0x02, 0x00, 0x01, 0xde, 0xad, 0xbe, 0xef]) + + self.assertEqual(expected_ipv6_packet_bytes, ipv6_packet_bytes) + + +class TestIPv6PacketFactory(unittest.TestCase): + + def test_should_create_IPv6Packet_with_MPL_extension_header_and_ICMP_upper_layer_protocol_when_to_bytes_method_is_called(self): + # GIVEN + ipv6_packet_bytes = bytearray([0x60, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x40, + 0xfd, 0x00, 0x12, 0x34, 0x45, 0x55, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x18, 0x00, + 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x3a, 0x00, 0x6d, 0x04, 0x40, 0x02, 0x00, 0x18, + 0x80, 0x00, 0x87, 0x12, 0x00, 0x00, 0x00, 0x02, + 0x80, 0x00, 0xc7, 0xbf, 0x00, 0x00, 0x00, 0x01, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41]) + + ipv6_factory = IPv6PacketFactory( + ehf={ + 0: HopByHopFactory( + hop_by_hop_options_factory=HopByHopOptionsFactory( + options_factories={ + 109: MPLOptionFactory() + } + ) + ) + }, + ulpf={ + 58: ICMPv6Factory(body_factories={ + 128: ICMPv6EchoBodyFactory() + }) + }) + + # WHEN + ipv6_packet = ipv6_factory.parse(io.BytesIO(ipv6_packet_bytes), any_message_info()) + + # THEN + self.assertEqual('fd00:1234:4555::ff:fe00:1800', ipv6_packet.ipv6_header.source_address.compressed) + self.assertEqual('ff03::1', ipv6_packet.ipv6_header.destination_address.compressed) + self.assertEqual(64, ipv6_packet.ipv6_header.hop_limit) + self.assertEqual(0, ipv6_packet.ipv6_header.next_header) + self.assertEqual(34, ipv6_packet.ipv6_header.payload_length) + self.assertEqual(0, ipv6_packet.ipv6_header.flow_label) + self.assertEqual(0, ipv6_packet.ipv6_header.traffic_class) + self.assertEqual(6, ipv6_packet.ipv6_header.version) + + self.assertEqual(1, ipv6_packet.extension_headers[0].options[0].value.S) + self.assertEqual(0, ipv6_packet.extension_headers[0].options[0].value.M) + self.assertEqual(0, ipv6_packet.extension_headers[0].options[0].value.V) + self.assertEqual(2, ipv6_packet.extension_headers[0].options[0].value.sequence) + self.assertEqual(bytearray([0x00, 0x18]), ipv6_packet.extension_headers[0].options[0].value.seed_id) + + self.assertEqual(34578, ipv6_packet.upper_layer_protocol.header.checksum) + self.assertEqual(128, ipv6_packet.upper_layer_protocol.header.type) + self.assertEqual(0, ipv6_packet.upper_layer_protocol.header.code) + self.assertEqual(0, ipv6_packet.upper_layer_protocol.body.identifier) + self.assertEqual(2, ipv6_packet.upper_layer_protocol.body.sequence_number) + self.assertEqual(b'\x80\x00\xc7\xbf\x00\x00\x00\x01AAAAAAAAAA', ipv6_packet.upper_layer_protocol.body.data) + + def test_should_create_IPv6Packet_without_any_extension_header_with_ICMP_upper_layer_protocol_when_to_bytes_method_is_called(self): + # GIVEN + ipv6_packet_bytes = bytearray([0x60, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x3A, 0x40, + 0xfd, 0x00, 0x12, 0x34, 0x45, 0x55, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x18, 0x00, + 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x80, 0x00, 0x87, 0x12, 0x00, 0x00, 0x00, 0x02, + 0x80, 0x00, 0xc7, 0xbf, 0x00, 0x00, 0x00, 0x01, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41]) + + ipv6_factory = IPv6PacketFactory( + ulpf={ + 58: ICMPv6Factory(body_factories={ + 128: ICMPv6EchoBodyFactory() + }) + }) + + # WHEN + ipv6_packet = ipv6_factory.parse(io.BytesIO(ipv6_packet_bytes), any_message_info()) + + # THEN + self.assertEqual('fd00:1234:4555::ff:fe00:1800', ipv6_packet.ipv6_header.source_address.compressed) + self.assertEqual('ff03::1', ipv6_packet.ipv6_header.destination_address.compressed) + self.assertEqual(64, ipv6_packet.ipv6_header.hop_limit) + self.assertEqual(58, ipv6_packet.ipv6_header.next_header) + self.assertEqual(26, ipv6_packet.ipv6_header.payload_length) + self.assertEqual(0, ipv6_packet.ipv6_header.flow_label) + self.assertEqual(0, ipv6_packet.ipv6_header.traffic_class) + self.assertEqual(6, ipv6_packet.ipv6_header.version) + + self.assertEqual(34578, ipv6_packet.upper_layer_protocol.header.checksum) + self.assertEqual(128, ipv6_packet.upper_layer_protocol.header.type) + self.assertEqual(0, ipv6_packet.upper_layer_protocol.header.code) + self.assertEqual(0, ipv6_packet.upper_layer_protocol.body.identifier) + self.assertEqual(2, ipv6_packet.upper_layer_protocol.body.sequence_number) + self.assertEqual(b'\x80\x00\xc7\xbf\x00\x00\x00\x01AAAAAAAAAA', ipv6_packet.upper_layer_protocol.body.data) + + def test_should_set_message_info_field_when_to_bytes_method_is_called(self): + # GIVEN + ipv6_packet_data = bytearray([0x60, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x3A, 0x40, + 0xfd, 0x00, 0x12, 0x34, 0x45, 0x55, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x18, 0x00, + 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x80, 0x00, 0x87, 0x12, 0x00, 0x00, 0x00, 0x02, + 0x80, 0x00, 0xc7, 0xbf, 0x00, 0x00, 0x00, 0x01, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41]) + + message_info = any_message_info() + message_info.source_ipv6 = "ff::" + message_info.destination_address = "ff::" + + factory = IPv6PacketFactory( + ulpf={ + 58: ICMPv6Factory(body_factories={ + 128: ICMPv6EchoBodyFactory() + }) + }) + + # WHEN + factory.parse(io.BytesIO(ipv6_packet_data), message_info) + + # THEN + self.assertEqual("fd00:1234:4555::ff:fe00:1800", message_info.source_ipv6.compressed) + self.assertEqual("ff03::1", message_info.destination_ipv6.compressed) + + +class TestUDPDatagram(unittest.TestCase): + + def test_should_creates_bytes_from_UDPHeader_and_payload_when_to_bytes_method_is_called(self): + # GIVEN + src_port = any_port() + dst_port = any_port() + checksum = any_checksum() + + payload = any_payload() + payload_length = len(payload) + 8 # UDP length consists of UDP header length and payload length + + udp_header = UDPHeader(src_port, dst_port, payload_length, checksum) + udp_payload = UDPBytesPayload(payload) + udp_dgram = UDPDatagram(udp_header, udp_payload) + + # WHEN + udp_dgram_bytes = udp_dgram.to_bytes() + + # THEN + expected_udp_dgram_bytes = struct.pack("!H", src_port) + struct.pack("!H", dst_port) + \ + struct.pack("!H", payload_length) + struct.pack("!H", checksum) + payload + + self.assertEqual(expected_udp_dgram_bytes, udp_dgram_bytes) + + +class TestICMPv6(unittest.TestCase): + + def test_should_creates_bytes_from_ICMPv6Header_and_body_when_to_bytes_method_is_called(self): + # GIVEN + _type = any_type() + code = any_code() + checksum = any_checksum() + body = any_body() + + icmpv6_header = ICMPv6Header(_type, code, checksum) + icmpv6_body = ICMPv6BytesBody(body) + icmpv6_msg = ICMPv6(icmpv6_header, icmpv6_body) + + # WHEN + actual = icmpv6_msg.to_bytes() + + # THEN + expected = bytearray([_type, code]) + struct.pack("!H", checksum) + body + + self.assertEqual(expected, actual) + + +class TestHopByHop(unittest.TestCase): + + def _calculate_hdr_ext_len(self, payload_len): + count = payload_len // 8 + rest = payload_len % 8 + + if rest != 0: + count += 1 + + if count == 0 and rest == 0: + return count + + return count - 1 + + def _calculate_required_padding(self, content_length): + excess_bytes = content_length & 0x7 + + if excess_bytes > 0: + return 8 - excess_bytes + + return 0 + + def create_padding(self, padding_length): + if padding_length == 1: + return bytearray([0x00]) + elif padding_length > 1: + padding_length -= 2 + return bytearray([0x01, padding_length]) + bytearray([0x00 for _ in range(padding_length)]) + else: + return bytearray() + + def test_should_create_bytes_from_HopByHop_when_to_bytes_method_is_called(self): + # GIVEN + next_header = any_next_header() + hop_by_hop_option = any_hop_by_hop_bytes_option() + hdr_ext_len = self._calculate_hdr_ext_len(2 + len(hop_by_hop_option)) + + hop_by_hop = HopByHop(next_header, [hop_by_hop_option]) + + # WHEN + data = hop_by_hop.to_bytes() + + # THEN + expected_data = bytearray([next_header, hdr_ext_len]) + hop_by_hop_option.to_bytes() + padding_length = self._calculate_required_padding(len(expected_data)) + expected_data += self.create_padding(padding_length) + + self.assertEqual(expected_data, data) + + +class TestMPLOption(unittest.TestCase): + + def test_should_convert_MPLOption_to_bytes_when_to_bytes_method_is_called(self): + # GIVEN + S = any_mpl_S() + M = any_mpl_M() + V = any_mpl_V() + sequence = any_mpl_sequence() + seed_id = any_mpl_seed_id(S) + + mpl_option = MPLOption(S, M, V, sequence, seed_id) + + # WHEN + data = mpl_option.to_bytes() + + # THEN + expected_data = bytearray([(S << 6) | (M << 5) | (V << 4), sequence]) + seed_id + self.assertEqual(expected_data, data) + + def test_should_create_MPLOption_when_to_bytes_method_is_called_with_data(self): + # GIVEN + S = any_mpl_S() + M = any_mpl_M() + V = any_mpl_V() + sequence = any_mpl_sequence() + seed_id = any_mpl_seed_id(S) + + data = bytearray([(S << 6) | (M << 5) | (V << 4), sequence]) + seed_id + + # WHEN + mpl_option = MPLOption.from_bytes(io.BytesIO(data)) + + # THEN + self.assertEqual(S, mpl_option.S) + self.assertEqual(M, mpl_option.M) + self.assertEqual(V, mpl_option.V) + self.assertEqual(sequence, mpl_option.sequence) + self.assertEqual(seed_id, mpl_option.seed_id) + + def test_check_if_mpl_seed_id_length_values_was_not_changed(self): + self.assertEqual(0, MPLOption._seed_id_length[0]) + self.assertEqual(2, MPLOption._seed_id_length[1]) + self.assertEqual(8, MPLOption._seed_id_length[2]) + self.assertEqual(16, MPLOption._seed_id_length[3]) + + def test_should_return_proper_length_when_len_is_called_with_mpl_option_object(self): + # GIVEN + S = any_mpl_S() + M = any_mpl_M() + V = any_mpl_V() + sequence = any_mpl_sequence() + seed_id = any_mpl_seed_id(S) + + mpl_option = MPLOption(S, M, V, sequence, seed_id) + + # WHEN + mpl_option_length = len(mpl_option) + + # THEN + SMV_and_sequence_length = 2 + self.assertEqual(SMV_and_sequence_length + len(seed_id), mpl_option_length) + + +class TestclassHopByHopOption(unittest.TestCase): + + def test_should_convert_HopByHopOption_to_bytes_when_to_bytes_method_is_called(self): + # GIVEN + length = any_length() + header = any_hop_by_hop_bytes_option_header(length) + value = any_hop_by_hop_bytes_value(length) + + hop_by_hop_option = HopByHopOption(header, value) + + # WHEN + data = hop_by_hop_option.to_bytes() + + # THEN + expected_data = header.to_bytes() + value.to_bytes() + self.assertEqual(expected_data, data) + + def test_should_return_length_of_HopByHopOption_when_len_is_called_with_hop_by_hop_option_object(self): + # GIVEN + length = any_length() + header = any_hop_by_hop_bytes_option_header(length) + value = any_hop_by_hop_bytes_value(length) + + hop_by_hop_option = HopByHopOption(header, value) + + # WHEN + hop_by_hop_option_length = len(hop_by_hop_option) + + # THEN + header_length = 2 + expected_hop_by_hop_option_length = header_length + length + self.assertEqual(expected_hop_by_hop_option_length, hop_by_hop_option_length) + + +class TestHopByHopOptionHeader(unittest.TestCase): + + def test_should_convert_HopByHopOptionHeader_to_bytes_when_to_bytes_method_is_called(self): + # GIVEN + _type = any_type() + length = any_length() + + hop_by_hop_option_header = HopByHopOptionHeader(_type, length) + + # WHEN + data = hop_by_hop_option_header.to_bytes() + + # THEN + expected_data = bytearray([_type, length]) + self.assertEqual(expected_data, data) + + def test_should_create_HopByHopOptionHeader_when_to_bytes_method_is_called_with_data(self): + # GIVEN + _type = any_type() + length = any_length() + + data = bytearray([_type, length]) + + # WHEN + option_header = HopByHopOptionHeader.from_bytes(io.BytesIO(data)) + + # THEN + self.assertEqual(_type, option_header.type) + self.assertEqual(length, option_header.length) + + def test_should_return_proper_length_when_len_is_called_with_HopByHopOptionHeader_object(self): + # GIVEN + _type = any_type() + length = any_length() + + option_header = HopByHopOptionHeader(_type, length) + + # WHEN + option_header_length = len(option_header) + + # THEN + expected_option_header_length = 2 + self.assertEqual(expected_option_header_length, option_header_length) + + +class TestHopByHopFactory(unittest.TestCase): + + def _calculate_hdr_ext_len(self, payload_length): + count = payload_length >> 3 + + if (payload_length & 0x7) == 0 and count > 0: + return count - 1 + + return count + + def padding(self, content_length): + excess_bytes = content_length & 0x7 + + if excess_bytes > 0: + padding_length = 8 - excess_bytes + + if padding_length == 1: + return bytearray([0x00]) + elif padding_length > 1: + padding_length -= 2 + return bytearray([0x01, padding_length]) + bytes([0x00 for _ in range(padding_length)]) + + return bytearray() + + def test_should_create_HopByHop_object_instance_when_to_bytes_method_is_called_with_data(self): + # GIVEN + hop_by_hop_option = any_hop_by_hop_mpl_option() + hop_by_hop_option_type = hop_by_hop_option.header.type + + next_header = any_next_header() + hdr_ext_len = self._calculate_hdr_ext_len(2 + len(hop_by_hop_option)) + + hop_by_hop_factory = HopByHopFactory( + hop_by_hop_options_factory=HopByHopOptionsFactory( + options_factories={ + hop_by_hop_option_type: MPLOptionFactory() + } + ) + ) + + data = bytearray([next_header, hdr_ext_len]) + hop_by_hop_option.to_bytes() + data += self.padding(len(data)) + + # WHEN + hop_by_hop = hop_by_hop_factory.parse(io.BytesIO(data), any_message_info()) + + # THEN + self.assertEqual(hop_by_hop_option.value.S, hop_by_hop.options[0].value.S) + self.assertEqual(hop_by_hop_option.value.V, hop_by_hop.options[0].value.V) + self.assertEqual(hop_by_hop_option.value.M, hop_by_hop.options[0].value.M) + self.assertEqual(hop_by_hop_option.value.sequence, hop_by_hop.options[0].value.sequence) + self.assertEqual(hop_by_hop_option.value.seed_id, hop_by_hop.options[0].value.seed_id) + + def test_should_raise_RuntimeError_when_no_option_factory_is_set_and_parse_method_is_called(self): + # GIVEN + hop_by_hop_option = any_hop_by_hop_mpl_option() + hop_by_hop_option_type = hop_by_hop_option.header.type + + next_header = any_next_header() + hdr_ext_len = self._calculate_hdr_ext_len(2 + len(hop_by_hop_option)) + + hop_by_hop_factory = HopByHopFactory(hop_by_hop_options_factory=HopByHopOptionsFactory()) + + data = bytes([next_header, hdr_ext_len]) + hop_by_hop_option.to_bytes() + data += self.padding(len(data)) + + # THEN + self.assertRaises(RuntimeError, hop_by_hop_factory.parse, io.BytesIO(data), any_message_info()) + + +class TestMPLOptionFactory(unittest.TestCase): + + def test_should_produce_MPLOption_from_bytes_when_to_bytes_method_is_called_with_data(self): + # GIVEN + S = any_mpl_S() + M = any_mpl_M() + V = any_mpl_V() + sequence = any_mpl_sequence() + seed_id = any_mpl_seed_id(S) + + SMV = (S << 6) | (M << 5) | (V << 4) + data = bytearray([SMV, sequence]) + seed_id + + factory = MPLOptionFactory() + + # WHEN + mpl_opt = factory.parse(io.BytesIO(data), any_message_info()) + + # THEN + self.assertEqual(mpl_opt.S, S) + self.assertEqual(mpl_opt.M, M) + self.assertEqual(mpl_opt.V, V) + self.assertEqual(mpl_opt.sequence, sequence) + self.assertEqual(mpl_opt.seed_id, seed_id) + + +class TestUDPDatagramFactory(unittest.TestCase): + + def test_should_produce_UDPDatagram_from_bytes_when_to_bytes_method_is_called_with_data(self): + # GIVEN + src_port = any_port() + dst_port = any_port() + checksum = any_checksum() + + payload = any_payload() + payload_length = len(payload) + len(UDPHeader(0, 0)) + + data = bytearray([(src_port >> 8), (src_port & 0xFF), + (dst_port >> 8), (dst_port & 0xFF), + (payload_length >> 8), (payload_length & 0xFF), + (checksum >> 8), (checksum & 0xFF)]) + payload + + factory = UDPDatagramFactory(UDPHeaderFactory(), {dst_port: UDPBytesPayloadFactory()}) + + # WHEN + udp_dgram = factory.parse(io.BytesIO(data), any_message_info()) + + # THEN + self.assertEqual(udp_dgram.header.src_port, src_port) + self.assertEqual(udp_dgram.header.dst_port, dst_port) + self.assertEqual(udp_dgram.header.payload_length, payload_length) + self.assertEqual(udp_dgram.header.checksum, checksum) + self.assertEqual(udp_dgram.payload.data, payload) + + +class TestICMPv6Factory(unittest.TestCase): + + def test_should_produce_ICMPv6_from_bytes_when_to_bytes_method_is_called_with_data(self): + # GIVEN + _type = any_type() + code = any_code() + checksum = any_checksum() + body = any_body() + + data = bytearray([_type, code, (checksum >> 8), (checksum & 0xFF)]) + body + + factory = ICMPv6Factory(body_factories={_type: ICMPv6BytesBodyFactory()}) + + # WHEN + icmpv6_msg = factory.parse(io.BytesIO(data), any_message_info()) + + # THEN + self.assertEqual(icmpv6_msg.header.type, _type) + self.assertEqual(icmpv6_msg.header.code, code) + self.assertEqual(icmpv6_msg.header.checksum, checksum) + self.assertEqual(icmpv6_msg.body.bytes, body) + + def test_should_raise_RuntimeError_when_method_parse_is_called_but_body_factory_is_not_present(self): + # GIVEN + _type = any_type() + code = any_code() + checksum = any_checksum() + body = any_body() + + data = bytes([_type, code, (checksum >> 8), (checksum & 0xFF)]) + body + + factory = ICMPv6Factory() + + # WHEN + self.assertRaises(RuntimeError, factory.parse, io.BytesIO(data), any_message_info()) + + +class TestUDPBytesPayload(unittest.TestCase): + + def test_should_create_UDPBytesPayload_when_from_bytes_class_method_is_called(self): + # GIVEN + data = any_data() + + # WHEN + actual = UDPBytesPayload.from_bytes(data) + + # THEN + self.assertEqual(data, actual.data) + + def test_should_return_exactly_the_same_data_as_passed_to_constructor_when_to_bytes_method_is_called(self): + # GIVEN + data = any_data() + payload = UDPBytesPayload(data) + + # WHEN + actual = payload.to_bytes() + + # THEN + self.assertEqual(data, actual) + + def test_should_return_the_same_length_as_data_passed_to_constructor_when_len_is_called_on_UDPBytesPayload_object(self): + # GIVEN + data = any_data() + payload = UDPBytesPayload(data) + + # WHEN + actual = len(payload) + + # THEN + self.assertEqual(len(data), actual) + + +class TestICMPv6EchoBody(unittest.TestCase): + + def test_convert_ICMPv6_echo_body_to_data_when_to_bytes_method_is_called(self): + # GIVEN + identifier = any_identifier() + sequence_number = any_sequence_number() + data = any_data() + + body = ICMPv6EchoBody(identifier, sequence_number, data) + + # WHEN + actual = body.to_bytes() + + # THEN + expected = bytearray([identifier >> 8, identifier & 0xFF, sequence_number >> 8, sequence_number & 0xFF]) + data + self.assertEqual(expected, actual) + + def test_should_create_ICMPv6EchoBody_from_data_when_from_bytes_classmethod_is_called(self): + # GIVEN + identifier = any_identifier() + sequence_number = any_sequence_number() + body_data = any_data() + + data = bytearray([(identifier >> 8), + (identifier & 0xFF), + (sequence_number >> 8), + (sequence_number & 0xFF)]) + data += body_data + + # WHEN + actual = ICMPv6EchoBody.from_bytes(io.BytesIO(data)) + + # THEN + self.assertEqual(identifier, actual.identifier) + self.assertEqual(sequence_number, actual.sequence_number) + self.assertEqual(body_data, actual.data) + + def test_should_build_ICMPv6EchoBody_from_well_know_values_when_to_bytes_method_is_called(self): + # GIVEN + body = ICMPv6EchoBody(0, 2, bytearray([0x80, 0x00, 0xc7, 0xbf, 0x00, 0x00, 0x00, 0x01, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41])) + + # WHEN + actual = body.to_bytes() + + # THEN + expected = bytearray([0x00, 0x00, 0x00, 0x02, 0x80, 0x00, 0xc7, 0xbf, + 0x00, 0x00, 0x00, 0x01, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41]) + + self.assertEqual(expected, actual) + + +class TestICMPv6EchoBodyFactory(unittest.TestCase): + + def test_should_build_ICMPv6EchoBody_when_to_bytes_method_is_called(self): + # GIVEN + identifier = any_identifier() + sequence_number = any_sequence_number() + body_data = any_data() + + data = bytearray([(identifier >> 8) & 0xFF, identifier & 0xFF, + (sequence_number >> 8) & 0xFF, sequence_number & 0xFF]) + body_data + + factory = ICMPv6EchoBodyFactory() + + # WHEN + actual = factory.parse(io.BytesIO(data), any_message_info()) + + # THEN + self.assertTrue(isinstance(actual, ICMPv6EchoBody)) + + self.assertEqual(identifier, actual.identifier) + self.assertEqual(sequence_number, actual.sequence_number) + self.assertEqual(body_data, actual.data) + + +class TestICMPv6DestinationUnreachable(unittest.TestCase): + + def test_should_convert_ICMPv6DestinationUnreachable_to_bytearray_when_to_bytes_method_is_called(self): + # GIVEN + data = any_data() + + icmpv6_dest_unreachable = ICMPv6DestinationUnreachable(data) + + # WHEN + actual_data = icmpv6_dest_unreachable.to_bytes() + + # THEN + self.assertEqual(bytearray([0x00, 0x00, 0x00, 0x00]) + data, actual_data) + + def test_should_convert_bytearray_to_ICMPv6DestinationUnreachable_when_from_bytes_method_is_called(self): + # GIVEN + data = any_data() + + # WHEN + icmpv6_dest_unreachable = ICMPv6DestinationUnreachable.from_bytes( + io.BytesIO(bytearray([0x00, 0x00, 0x00, 0x00]) + data)) + + # THEN + self.assertEqual(data, icmpv6_dest_unreachable.data) + + def test_should_raise_RuntimeError_when_from_bytes_method_is_called(self): + # GIVEN + data = any_data() + + unused = random.randint(1, 1 << 32) + + # WHEN + self.assertRaises(RuntimeError, ICMPv6DestinationUnreachable.from_bytes, + io.BytesIO(bytearray(struct.pack(">I", unused)) + data)) + + +class TestUDPHeaderFactory(unittest.TestCase): + + def test_should_create_UDPHeader_when_to_bytes_method_is_called(self): + # GIVEN + factory = UDPHeaderFactory() + + src_port = any_port() + dst_port = any_port() + payload_length = any_payload_length() + checksum = any_checksum() + + data = struct.pack("!H", src_port) + struct.pack("!H", dst_port) + \ + struct.pack("!H", payload_length) + struct.pack("!H", checksum) + + # WHEN + udp_header = factory.parse(io.BytesIO(data), any_message_info()) + + # THEN + self.assertEqual(src_port, udp_header.src_port) + self.assertEqual(dst_port, udp_header.dst_port) + self.assertEqual(payload_length, udp_header.payload_length) + self.assertEqual(checksum, udp_header.checksum) + + +class TestHopByHopOptionsFactory(unittest.TestCase): + + def test_should_create_option_from_bytearray_when_to_bytes_method_is_called(self): + # GIVEN + + class DummyOptionFactory: + + def parse(self, data, message_info): + return data.read() + + factory = HopByHopOptionsFactory(options_factories={2: DummyOptionFactory()}) + + data = bytearray([0x02, 0x03, 0x11, 0x22, 0x33, 0x01, 0x00]) + + # WHEN + actual_options = factory.parse(io.BytesIO(data), any_message_info()) + + # THEN + self.assertEqual(1, len(actual_options)) + self.assertEqual(2, actual_options[0].header.type) + self.assertEqual(3, actual_options[0].header.length) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/scripts/thread-cert/test_lowpan.py b/tests/scripts/thread-cert/test_lowpan.py new file mode 100644 index 000000000..a68c26133 --- /dev/null +++ b/tests/scripts/thread-cert/test_lowpan.py @@ -0,0 +1,2656 @@ +#!/usr/bin/python +# +# Copyright (c) 2016, 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 io +import random +import struct +import unittest + +import common +import config +import ipv6 +import lowpan + + +def create_default_lowpan_parser(context_manager): + dst_port_factories = { + 5684: ipv6.UDPBytesPayloadFactory() + } + + return lowpan.LowpanParser( + lowpan_mesh_header_factory=lowpan.LowpanMeshHeaderFactory(), + lowpan_decompressor=config.create_default_lowpan_decompressor(context_manager), + lowpan_fragements_buffers_manager=lowpan.LowpanFragmentsBuffersManager(), + ipv6_packet_factory=ipv6.IPv6PacketFactory( + ehf=config.create_default_ipv6_extension_headers_factories(), + ulpf={ + 17: ipv6.UDPDatagramFactory( + udp_header_factory=ipv6.UDPHeaderFactory(), + dst_port_factories=dst_port_factories), + 58: ipv6.ICMPv6Factory( + body_factories=config.create_default_ipv6_icmp_body_factories() + ) + } + ) + ) + + +def any_tf(): + return random.getrandbits(2) + + +def any_nh(): + return random.getrandbits(1) + + +def any_hlim(): + return random.getrandbits(2) + + +def any_cid(): + return random.getrandbits(1) + + +def any_sac(): + return random.getrandbits(1) + + +def any_sam(): + return random.getrandbits(2) + + +def any_m(): + return random.getrandbits(1) + + +def any_dac(): + return random.getrandbits(1) + + +def any_dam(): + return random.getrandbits(2) + + +def any_ecn(): + return random.getrandbits(2) + + +def any_dscp(): + return random.getrandbits(6) + + +def any_flow_label(): + return random.getrandbits(6) + + +def any_hop_limit(): + return random.getrandbits(8) + + +def any_src_addr(): + return bytearray([random.getrandbits(8) for _ in xrange(16)]) + + +def any_dst_addr(): + return bytearray([random.getrandbits(8) for _ in xrange(16)]) + + +def any_eui64(): + return bytearray([random.getrandbits(8) for _ in xrange(8)]) + + +def any_rloc16(): + return bytearray([random.getrandbits(8) for _ in xrange(2)]) + + +def any_48bits_addr(): + return bytearray([random.getrandbits(8) for _ in xrange(6)]) + + +def any_32bits_addr(): + return bytearray([random.getrandbits(8) for _ in xrange(4)]) + + +def any_8bits_addr(): + return bytearray([random.getrandbits(8)]) + + +def any_c(): + return random.getrandbits(1) + + +def any_p(): + return random.getrandbits(2) + + +def any_src_port(): + return random.getrandbits(16) + + +def any_dst_port(): + return random.getrandbits(16) + + +def any_compressable_src_port(): + return 0xf000 + random.getrandbits(8) + + +def any_compressable_dst_port(): + return 0xf000 + random.getrandbits(8) + + +def any_nibble_src_port(): + return 0xf0b0 + random.getrandbits(4) + + +def any_nibble_dst_port(): + return 0xf0b0 + random.getrandbits(4) + + +def any_checksum(): + return random.getrandbits(16) + + +def any_next_header(): + return random.getrandbits(8) + + +def any_sci(): + return random.getrandbits(4) + + +def any_dci(): + return random.getrandbits(4) + + +def any_src_mac_addr(): + return bytearray([random.getrandbits(8) for _ in xrange(8)]) + + +def any_dst_mac_addr(): + return bytearray([random.getrandbits(8) for _ in xrange(8)]) + + +def any_context(): + prefix = bytearray([random.getrandbits(8) for _ in xrange(random.randint(2, 15))]) + prefix_length = len(prefix) + return lowpan.Context(prefix, prefix_length * 8) + + +def any_mac_address(): + length = random.choice([2, 8]) + if length == 2: + return common.MacAddress.from_rloc16(bytearray([random.getrandbits(8) for _ in xrange(length)])) + elif length == 8: + return common.MacAddress.from_eui64(bytearray([random.getrandbits(8) for _ in xrange(length)])) + + +def any_hops_left(): + return random.getrandbits(4) + + +def any_data(length=None): + length = length if length is not None else random.randint(1, 64) + return bytearray([random.getrandbits(8) for _ in xrange(length)]) + + +def any_datagram_size(): + return random.getrandbits(11) + + +def any_datagram_tag(): + return random.getrandbits(16) + + +def any_datagram_offset(): + return random.getrandbits(8) + + +class TestLowpanIPHC(unittest.TestCase): + + def test_should_create_LowpanIPHC_object_when_from_bytes_classmethod_is_called(self): + # GIVEN + tf = any_tf() + nh = any_nh() + hlim = any_hlim() + cid = any_cid() + sac = any_sac() + sam = any_sam() + m = any_m() + dac = any_dac() + dam = any_dam() + + byte0 = (3 << 5) | (tf << 3) | (nh << 2) | hlim + byte1 = (cid << 7) | (sac << 6) | (sam << 4) | (m << 3) | (dac << 2) | dam + + data_bytes = bytearray([byte0, byte1]) + + # WHEN + actual = lowpan.LowpanIPHC.from_bytes(data_bytes) + + # THEN + self.assertEqual(tf, actual.tf) + self.assertEqual(nh, actual.nh) + self.assertEqual(hlim, actual.hlim) + self.assertEqual(cid, actual.cid) + self.assertEqual(sac, actual.sac) + self.assertEqual(sam, actual.sam) + self.assertEqual(m, actual.m) + self.assertEqual(dac, actual.dac) + self.assertEqual(dam, actual.dam) + + +class TestLowpanParser(unittest.TestCase): + + def test_should_parse_6lowpan_packet_with_not_compressed_udp_and_without_hop_by_hop_extension_header_when_decompress_method_is_called(self): + # GIVEN + lowpan_packet = bytearray([0x7a, 0x33, 0x11, 0x16, 0x33, 0x16, 0x34, 0x00, + 0x14, 0xcf, 0x63, 0x80, 0x00, 0xfa, 0xa5, 0x0b, + 0xc0, 0x00, 0x04, 0x4e, 0x92, 0xbb, 0x53]) + + ipv6_packet = bytearray([0x60, 0x00, 0x00, 0x00, 0x00, 0x14, 0x11, 0x40, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x36, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x17, + 0x16, 0x33, 0x16, 0x34, 0x00, 0x14, 0xcf, 0x63, + 0x80, 0x00, 0xfa, 0xa5, 0x0b, 0xc0, 0x00, 0x04, + 0x4e, 0x92, 0xbb, 0x53]) + + message_info = common.MessageInfo() + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x00, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00])) + message_info.destination_mac_address = common.MacAddress.from_eui64( + bytearray([0x34, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x17])) + + parser = create_default_lowpan_parser(context_manager=None) + + # WHEN + actual_ipv6_packet = parser.parse(io.BytesIO(lowpan_packet), message_info) + + # THEN + self.assertEqual(ipv6_packet, actual_ipv6_packet.to_bytes()) + + def test_should_parse_6lowpan_packet_with_compressed_udp_and_without_hop_by_hop_extension_header_when_decompress_method_is_called(self): + # GIVEN + lowpan_packet = bytearray([0x7e, 0x33, 0xf0, 0x16, 0x33, 0x16, 0x34, 0x04, + 0xd2, 0x80, 0x00, 0xfa, 0xa5, 0x0b, 0xc0, 0x00, + 0x04, 0x4e, 0x92, 0xbb, 0x53]) + + ipv6_packet = bytearray([0x60, 0x00, 0x00, 0x00, 0x00, 0x14, 0x11, 0x40, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x36, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x17, + 0x16, 0x33, 0x16, 0x34, 0x00, 0x14, 0xcf, 0x63, + 0x80, 0x00, 0xfa, 0xa5, 0x0b, 0xc0, 0x00, 0x04, + 0x4e, 0x92, 0xbb, 0x53]) + + message_info = common.MessageInfo() + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x00, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00])) + message_info.destination_mac_address = common.MacAddress.from_eui64( + bytearray([0x34, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x17])) + + parser = create_default_lowpan_parser(context_manager=None) + + # WHEN + actual_ipv6_packet = parser.parse(io.BytesIO(lowpan_packet), message_info) + + # THEN + self.assertEqual(ipv6_packet, actual_ipv6_packet.to_bytes()) + + def test_should_parse_6lowpan_packet_with_not_compressed_udp_and_with_not_compressed_hop_by_hop_extension_header_when_decompress_method_is_called(self): + # GIVEN + lowpan_packet = bytearray([0x7a, 0x33, 0x00, 0x11, 0x00, 0x6d, 0x04, 0x40, + 0x02, 0x00, 0x18, 0x16, 0x33, 0x16, 0x34, 0x00, + 0x0c, 0x04, 0xd2, 0x80, 0x00, 0xfa, 0xa5, 0x0b, + 0xc0, 0x00, 0x04, 0x4e, 0x92, 0xbb, 0x53]) + + ipv6_packet = bytearray([0x60, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x40, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x36, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x17, + 0x11, 0x00, 0x6d, 0x04, 0x40, 0x02, 0x00, 0x18, + 0x16, 0x33, 0x16, 0x34, 0x00, 0x14, 0xcf, 0x63, + 0x80, 0x00, 0xfa, 0xa5, 0x0b, 0xc0, 0x00, 0x04, + 0x4e, 0x92, 0xbb, 0x53]) + + message_info = common.MessageInfo() + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x00, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00])) + message_info.destination_mac_address = common.MacAddress.from_eui64( + bytearray([0x34, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x17])) + + parser = create_default_lowpan_parser(context_manager=None) + + # WHEN + actual_ipv6_packet = parser.parse(io.BytesIO(lowpan_packet), message_info) + + # THEN + self.assertEqual(ipv6_packet, actual_ipv6_packet.to_bytes()) + + def test_should_parse_6lowpan_packet_with_not_compressed_udp_and_with_compressed_hop_by_hop_extension_header_when_decompress_method_is_called(self): + # GIVEN + lowpan_packet = bytearray([0x7e, 0x33, 0xe0, 0x11, 0x06, 0x6d, 0x04, 0x40, + 0x02, 0x00, 0x18, 0x16, 0x33, 0x16, 0x34, 0x00, + 0x0c, 0x04, 0xd2, 0x80, 0x00, 0xfa, 0xa5, 0x0b, + 0xc0, 0x00, 0x04, 0x4e, 0x92, 0xbb, 0x53]) + + ipv6_packet = bytearray([0x60, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x40, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x36, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x17, + 0x11, 0x00, 0x6d, 0x04, 0x40, 0x02, 0x00, 0x18, + 0x16, 0x33, 0x16, 0x34, 0x00, 0x14, 0xcf, 0x63, + 0x80, 0x00, 0xfa, 0xa5, 0x0b, 0xc0, 0x00, 0x04, + 0x4e, 0x92, 0xbb, 0x53]) + + message_info = common.MessageInfo() + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x00, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00])) + message_info.destination_mac_address = common.MacAddress.from_eui64( + bytearray([0x34, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x17])) + + parser = create_default_lowpan_parser(context_manager=None) + + # WHEN + actual_ipv6_packet = parser.parse(io.BytesIO(lowpan_packet), message_info) + + # THEN + self.assertEqual(ipv6_packet, actual_ipv6_packet.to_bytes()) + + def test_should_parse_6lowpan_packet_with_compressed_udp_and_with_compressed_hop_by_hop_extension_header_when_decompress_method_is_called(self): + # GIVEN + lowpan_packet = bytearray([0x7e, 0x33, 0xe1, 0x06, 0x6d, 0x04, 0x40, 0x02, + 0x00, 0x18, 0xf0, 0x16, 0x33, 0x16, 0x34, 0x04, + 0xd2, 0x80, 0x00, 0xfa, 0xa5, 0x0b, 0xc0, 0x00, + 0x04, 0x4e, 0x92, 0xbb, 0x53]) + + ipv6_packet = bytearray([0x60, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x40, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x36, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x17, + 0x11, 0x00, 0x6d, 0x04, 0x40, 0x02, 0x00, 0x18, + 0x16, 0x33, 0x16, 0x34, 0x00, 0x14, 0xcf, 0x63, + 0x80, 0x00, 0xfa, 0xa5, 0x0b, 0xc0, 0x00, 0x04, + 0x4e, 0x92, 0xbb, 0x53]) + + message_info = common.MessageInfo() + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x00, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00])) + message_info.destination_mac_address = common.MacAddress.from_eui64( + bytearray([0x34, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x17])) + + parser = create_default_lowpan_parser(context_manager=None) + + # WHEN + actual_ipv6_packet = parser.parse(io.BytesIO(lowpan_packet), message_info) + + # THEN + self.assertEqual(ipv6_packet, actual_ipv6_packet.to_bytes()) + + def test_should_parse_6lowpan_packet_with_compressed_icmp_and_without_compressed_hop_by_hop_extension_header_when_decompress_method_is_called(self): + # GIVEN + lowpan_packet = bytearray([0x7a, 0xd5, 0xaa, 0x3a, 0x02, 0x99, 0x99, 0xff, + 0xfe, 0x22, 0x11, 0x01, 0x36, 0x29, 0x96, 0xff, + 0xfe, 0xac, 0xff, 0x18, 0x80, 0x00, 0xfa, 0xa5, + 0x0b, 0xc0, 0x00, 0x04, 0x4e, 0x92, 0xbb, 0x53]) + + ipv6_packet = bytearray([0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x3a, 0x40, + 0x20, 0x00, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x01, + 0x20, 0x00, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, + 0x36, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x18, + 0x80, 0x00, 0x97, 0xf3, 0x0b, 0xc0, 0x00, 0x04, + 0x4e, 0x92, 0xbb, 0x53]) + + message_info = common.MessageInfo() + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x00, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00])) + message_info.destination_mac_address = common.MacAddress.from_eui64( + bytearray([0x34, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x17])) + + context_manager = lowpan.ContextManager() + context_manager[10] = lowpan.Context(prefix="2000:0db8::/64") + + parser = create_default_lowpan_parser(context_manager) + + # WHEN + actual_ipv6_packet = parser.parse(io.BytesIO(lowpan_packet), message_info) + + # THEN + self.assertEqual(ipv6_packet, actual_ipv6_packet.to_bytes()) + + def test_should_parse_6lowpan_packet_with_compressed_icmp_and_without_compressed_hop_by_hop_extension_header_when_decompress_method_is_called_1(self): + # GIVEN + lowpan_packet = bytearray([0x7a, 0xd5, 0xaa, 0x3a, 0x02, 0x99, 0x99, 0xff, + 0xfe, 0x22, 0x11, 0x01, 0x36, 0x29, 0x96, 0xff, + 0xfe, 0xac, 0xff, 0x18, 0x80, 0x00, 0xfa, 0xa5, + 0x0b, 0xc0, 0x00, 0x04, 0x4e, 0x92, 0xbb, 0x53]) + + ipv6_packet = bytearray([0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x3a, 0x40, + 0x20, 0x00, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x01, + 0x20, 0x00, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, + 0x36, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x18, + 0x80, 0x00, 0x97, 0xf3, 0x0b, 0xc0, 0x00, 0x04, + 0x4e, 0x92, 0xbb, 0x53]) + + message_info = common.MessageInfo() + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x00, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00])) + message_info.destination_mac_address = common.MacAddress.from_eui64( + bytearray([0x34, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x17])) + + context_manager = lowpan.ContextManager() + context_manager[10] = lowpan.Context(prefix="2000:0db8::/64") + + parser = create_default_lowpan_parser(context_manager) + + # WHEN + actual_ipv6_packet = parser.parse(io.BytesIO(lowpan_packet), message_info) + + # THEN + self.assertEqual(ipv6_packet, actual_ipv6_packet.to_bytes()) + + def test_should_parse_6lowpan_packet_with_compressed_icmp_and_without_compressed_hop_by_hop_extension_header_when_decompress_method_is_called_2(self): + # GIVEN + lowpan_packet = bytearray([0x7a, 0xf0, 0xa0, 0x3a, 0x20, 0x0d, 0x14, 0x56, + 0x12, 0x55, 0x00, 0x00, 0x25, 0x14, 0x46, 0xff, + 0xfe, 0xdd, 0x2a, 0xfe, 0x80, 0x00, 0xfa, 0xa5, + 0x0b, 0xc0, 0x00, 0x04, 0x4e, 0x92, 0xbb, 0x53]) + + ipv6_packet = bytearray([0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x3a, 0x40, + 0x20, 0x00, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00, + 0x20, 0x0d, 0x14, 0x56, 0x12, 0x55, 0x00, 0x00, + 0x25, 0x14, 0x46, 0xff, 0xfe, 0xdd, 0x2a, 0xfe, + 0x80, 0x00, 0xb3, 0xf3, 0x0b, 0xc0, 0x00, 0x04, + 0x4e, 0x92, 0xbb, 0x53]) + + message_info = common.MessageInfo() + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x00, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00])) + message_info.destination_mac_address = common.MacAddress.from_eui64( + bytearray([0x34, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x17])) + + context_manager = lowpan.ContextManager() + context_manager[10] = lowpan.Context(prefix="2000:0db8::/64") + + parser = create_default_lowpan_parser(context_manager) + + # WHEN + actual_ipv6_packet = parser.parse(io.BytesIO(lowpan_packet), message_info) + + # THEN + self.assertEqual(ipv6_packet, actual_ipv6_packet.to_bytes()) + + def test_should_parse_6lowpan_packet_with_compressed_icmp_and_without_compressed_hop_by_hop_extension_header_when_decompress_method_is_called_3(self): + # GIVEN + lowpan_packet = bytearray([0x7a, 0xd5, 0xaa, 0x3a, 0x02, 0x99, 0x99, 0xff, + 0xfe, 0x22, 0x11, 0x01, 0x36, 0x29, 0x96, 0xff, + 0xfe, 0xac, 0xff, 0x18, 0x80, 0x00, 0xfa, 0xa5, + 0x0b, 0xc0, 0x00, 0x04, 0x4e, 0x92, 0xbb, 0x53]) + + ipv6_packet = bytearray([0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x3a, 0x40, + 0x20, 0x00, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x01, + 0x20, 0x00, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, + 0x36, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x18, + 0x80, 0x00, 0x97, 0xf3, 0x0b, 0xc0, 0x00, 0x04, + 0x4e, 0x92, 0xbb, 0x53]) + + message_info = common.MessageInfo() + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x00, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00])) + message_info.destination_mac_address = common.MacAddress.from_eui64( + bytearray([0x34, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x17])) + + context_manager = lowpan.ContextManager() + context_manager[10] = lowpan.Context(prefix="2000:0db8::/64") + + parser = create_default_lowpan_parser(context_manager) + + # WHEN + actual_ipv6_packet = parser.parse(io.BytesIO(lowpan_packet), message_info) + + # THEN + self.assertEqual(ipv6_packet, actual_ipv6_packet.to_bytes()) + + def test_should_parse_6lowpan_packet_with_compressed_icmp_and_without_compressed_hop_by_hop_extension_header_when_decompress_method_is_called_4(self): + # GIVEN + lowpan_packet = bytearray([0x7a, 0xf5, 0xaa, 0x3a, 0x36, 0x29, 0x96, 0xff, + 0xfe, 0xac, 0xff, 0x18, 0x80, 0x00, 0xfa, 0xa5, + 0x0b, 0xc0, 0x00, 0x04, 0x4e, 0x92, 0xbb, 0x53]) + + ipv6_packet = bytearray([0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x3a, 0x40, + 0x20, 0x00, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00, + 0x20, 0x00, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, + 0x36, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x18, + 0x80, 0x00, 0x97, 0xf4, 0x0b, 0xc0, 0x00, 0x04, + 0x4e, 0x92, 0xbb, 0x53]) + + message_info = common.MessageInfo() + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x00, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00])) + message_info.destination_mac_address = common.MacAddress.from_eui64( + bytearray([0x34, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x17])) + + context_manager = lowpan.ContextManager() + context_manager[10] = lowpan.Context(prefix="2000:0db8::/64") + + parser = create_default_lowpan_parser(context_manager) + + # WHEN + actual_ipv6_packet = parser.parse(io.BytesIO(lowpan_packet), message_info) + + # THEN + self.assertEqual(ipv6_packet, actual_ipv6_packet.to_bytes()) + + def test_should_parse_6lowpan_packet_with_compressed_icmp_and_without_compressed_hop_by_hop_extension_header_when_decompress_method_is_called_5(self): + # GIVEN + lowpan_packet = bytearray([0x7a, 0xf7, 0xac, 0x3a, 0x80, 0x00, 0xfa, 0xa5, + 0x0b, 0xc0, 0x00, 0x04, 0x4e, 0x92, 0xbb, 0x53]) + + ipv6_packet = bytearray([0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x3a, 0x40, + 0x20, 0x00, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00, + 0x20, 0x0d, 0x14, 0x56, 0x12, 0x55, 0x00, 0x00, + 0x25, 0x14, 0x46, 0xff, 0xfe, 0xdd, 0x2a, 0xfe, + 0x80, 0x00, 0xb3, 0xf3, 0x0b, 0xc0, 0x00, 0x04, + 0x4e, 0x92, 0xbb, 0x53]) + + message_info = common.MessageInfo() + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x00, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00])) + message_info.destination_mac_address = common.MacAddress.from_eui64( + bytearray([0x34, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x17])) + + context_manager = lowpan.ContextManager() + context_manager[10] = lowpan.Context(prefix="2000:0db8::/64") + context_manager[12] = lowpan.Context(prefix="200d:1456:1255:0000:2514:46ff:fedd:2afe/128") + + parser = create_default_lowpan_parser(context_manager) + + # WHEN + actual_ipv6_packet = parser.parse(io.BytesIO(lowpan_packet), message_info) + + # THEN + self.assertEqual(ipv6_packet, actual_ipv6_packet.to_bytes()) + + def test_should_parse_6lowpan_packet_with_compressed_icmp_and_without_compressed_hop_by_hop_extension_header_when_decompress_method_is_called_6(self): + # GIVEN + lowpan_packet = bytearray([0x7a, 0xf0, 0xc0, 0x3a, 0x20, 0x0d, 0x14, 0x56, + 0x12, 0x54, 0x00, 0x00, 0x12, 0x54, 0x11, 0xff, + 0xfe, 0x1c, 0x7e, 0xff, 0x80, 0x00, 0xfa, 0xa5, + 0x0b, 0xc0, 0x00, 0x04, 0x4e, 0x92, 0xbb, 0x53]) + + ipv6_packet = bytearray([0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x3a, 0x40, + 0x20, 0x0d, 0x14, 0x56, 0x12, 0x55, 0x00, 0x00, + 0x25, 0x14, 0x46, 0xff, 0xfe, 0xdd, 0x2a, 0xfe, + 0x20, 0x0d, 0x14, 0x56, 0x12, 0x54, 0x00, 0x00, + 0x12, 0x54, 0x11, 0xff, 0xfe, 0x1c, 0x7e, 0xff, + 0x80, 0x00, 0xa5, 0x40, 0x0b, 0xc0, 0x00, 0x04, + 0x4e, 0x92, 0xbb, 0x53]) + + message_info = common.MessageInfo() + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x00, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00])) + message_info.destination_mac_address = common.MacAddress.from_eui64( + bytearray([0x34, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x17])) + + context_manager = lowpan.ContextManager() + context_manager[12] = lowpan.Context(prefix="200d:1456:1255:0000:2514:46ff:fedd:2afe/128") + + parser = create_default_lowpan_parser(context_manager) + + # WHEN + actual_ipv6_packet = parser.parse(io.BytesIO(lowpan_packet), message_info) + + # THEN + self.assertEqual(ipv6_packet, actual_ipv6_packet.to_bytes()) + + def test_should_parse_6lowpan_packet_with_compressed_icmp_and_without_compressed_hop_by_hop_extension_header_when_decompress_method_is_called_7(self): + # GIVEN + lowpan_packet = bytearray([0x7a, 0xd0, 0xd0, 0x3a, 0x00, 0x02, 0x98, 0xff, + 0xfe, 0x22, 0x12, 0x00, 0x20, 0x0d, 0x14, 0x56, + 0x12, 0x55, 0x00, 0x00, 0x25, 0x14, 0x46, 0xff, + 0xfe, 0xdd, 0x2a, 0xfe, 0x80, 0x00, 0xfa, 0xa5, + 0x0b, 0xc0, 0x00, 0x04, 0x4e, 0x92, 0xbb, 0x53]) + + ipv6_packet = bytearray([0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x3a, 0x40, + 0xaa, 0xbb, 0xcc, 0xdd, 0x00, 0x00, 0x00, 0x00, + 0x77, 0x82, 0x98, 0xff, 0xfe, 0x22, 0x12, 0x00, + 0x20, 0x0d, 0x14, 0x56, 0x12, 0x55, 0x00, 0x00, + 0x25, 0x14, 0x46, 0xff, 0xfe, 0xdd, 0x2a, 0xfe, + 0x80, 0x00, 0xf5, 0x28, 0x0b, 0xc0, 0x00, 0x04, + 0x4e, 0x92, 0xbb, 0x53]) + + message_info = common.MessageInfo() + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x00, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00])) + message_info.destination_mac_address = common.MacAddress.from_eui64( + bytearray([0x34, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x17])) + + context_manager = lowpan.ContextManager() + context_manager[13] = lowpan.Context(prefix="AABB:CCDD:0000:0000:7796::/75") + + parser = create_default_lowpan_parser(context_manager) + + # WHEN + actual_ipv6_packet = parser.parse(io.BytesIO(lowpan_packet), message_info) + + # THEN + self.assertEqual(ipv6_packet, actual_ipv6_packet.to_bytes()) + + def test_should_parse_6lowpan_packet_with_compressed_icmp_and_without_compressed_hop_by_hop_extension_header_when_decompress_method_is_called_8(self): + # GIVEN + lowpan_packet = bytearray([0x7a, 0xf0, 0xd0, 0x3a, 0x20, 0x0d, 0x14, 0x56, + 0x12, 0x55, 0x00, 0x00, 0x25, 0x14, 0x46, 0xff, + 0xfe, 0xdd, 0x2a, 0xfe, 0x80, 0x00, 0xfa, 0xa5, + 0x0b, 0xc0, 0x00, 0x04, 0x4e, 0x92, 0xbb, 0x53]) + + ipv6_packet = bytearray([0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x3a, 0x40, + 0xaa, 0xbb, 0xcc, 0xdd, 0x00, 0x00, 0x00, 0x00, + 0x77, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00, + 0x20, 0x0d, 0x14, 0x56, 0x12, 0x55, 0x00, 0x00, + 0x25, 0x14, 0x46, 0xff, 0xfe, 0xdd, 0x2a, 0xfe, + 0x80, 0x00, 0xf5, 0x11, 0x0b, 0xc0, 0x00, 0x04, + 0x4e, 0x92, 0xbb, 0x53]) + + message_info = common.MessageInfo() + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x00, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00])) + message_info.destination_mac_address = common.MacAddress.from_eui64( + bytearray([0x34, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x17])) + + context_manager = lowpan.ContextManager() + context_manager[13] = lowpan.Context(prefix="AABB:CCDD:0000:0000:7796::/75") + + parser = create_default_lowpan_parser(context_manager) + + # WHEN + actual_ipv6_packet = parser.parse(io.BytesIO(lowpan_packet), message_info) + + # THEN + self.assertEqual(ipv6_packet, actual_ipv6_packet.to_bytes()) + + def test_should_parse_6lowpan_packet_with_compressed_icmp_and_without_compressed_hop_by_hop_extension_header_when_decompress_method_is_called_9(self): + # GIVEN + lowpan_packet = bytearray([0x7a, 0xf0, 0xd0, 0x3a, 0x20, 0x0d, 0x14, 0x56, + 0x12, 0x55, 0x00, 0x00, 0x25, 0x14, 0x46, 0xff, + 0xfe, 0xdd, 0x2a, 0xfe, 0x80, 0x00, 0xfa, 0xa5, + 0x0b, 0xc0, 0x00, 0x04, 0x4e, 0x92, 0xbb, 0x53]) + + ipv6_packet = bytearray([0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x3a, 0x40, + 0xaa, 0xbb, 0xcc, 0xdd, 0x00, 0x00, 0x00, 0x00, + 0x77, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00, + 0x20, 0x0d, 0x14, 0x56, 0x12, 0x55, 0x00, 0x00, + 0x25, 0x14, 0x46, 0xff, 0xfe, 0xdd, 0x2a, 0xfe, + 0x80, 0x00, 0xf5, 0x11, 0x0b, 0xc0, 0x00, 0x04, + 0x4e, 0x92, 0xbb, 0x53]) + + message_info = common.MessageInfo() + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x00, 0x99, 0x99, 0xff, 0xfe, 0x22, 0x11, 0x00])) + message_info.destination_mac_address = common.MacAddress.from_eui64( + bytearray([0x34, 0x29, 0x96, 0xff, 0xfe, 0xac, 0xff, 0x17])) + context_manager = lowpan.ContextManager() + context_manager[13] = lowpan.Context(prefix="AABB:CCDD:0000:0000:7796::/75") + + parser = create_default_lowpan_parser(context_manager) + + # WHEN + actual_ipv6_packet = parser.parse(io.BytesIO(lowpan_packet), message_info) + + # THEN + self.assertEqual(ipv6_packet, actual_ipv6_packet.to_bytes()) + + def test_should_defragment_big_IPv6_packet_when_parse_method_is_called_with_fragments_in_random_order(self): + # GIVEN + fragment_1 = bytearray([0xC5, 0x00, 0x31, 0x9F, 0x7A, 0x33, 0x3A, + 0x80, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, 0x4E, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0x43, 0x53, 0x11, 0x44, 0x66, + 0x4E, 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, + 0x80, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0xBB, 0x53, 0x11, 0x4C, 0x66, 0x4E]) + + fragment_2 = bytearray([0xE5, 0x00, 0x31, 0x9F, 0x11, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0xa3, 0x53, 0x11, 0x44, 0x66, + 0xFE, 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, + 0x80, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0x1B, 0x53, 0x11, 0x44, 0x66, 0x4E, + 0x22, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA]) + + fragment_3 = bytearray([0xE5, 0x00, 0x31, 0x9F, 0x1D, + 0x44, 0x54, 0x12, 0xD3, 0x53, 0x11, 0x44, 0x66, + 0x4E, 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, + 0xC0, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0xBB, 0x53, 0x11, 0x44, 0xCC, 0x4E, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0x43, 0x53, 0x11, 0x44, 0x66, + 0x4E, 0x92, 0xBC, 0x53, 0x1A, 0x44, 0x66, 0x77]) + + fragment_4 = bytearray([0xE5, 0x00, 0x31, 0x9F, 0x29, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, + 0x80, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, 0x4E, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0x43, 0x53, 0x11, 0x44, 0x66, + 0x4E, 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99]) + + fragment_5 = bytearray([0xE5, 0x00, 0x31, 0x9F, 0x35, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, + 0x80, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0xBB, 0x53, 0x11, 0x4C, 0x66, 0x4E, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0xa3, 0x53, 0x11, 0x44, 0x66, + 0xFE, 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A]) + + fragment_6 = bytearray([0xE5, 0x00, 0x31, 0x9F, 0x41, + 0x80, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0x1B, 0x53, 0x11, 0x44, 0x66, 0x4E, + 0x22, 0xBB, 0x53, 0x1A, 0x44, 0x67, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0xD3, 0x53, 0x11, 0x44, 0x66, + 0x4E, 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, + 0xC0, 0x00, 0xFA, 0x15, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0xBB, 0x53, 0x11, 0x44, 0xCC, 0x4E]) + + fragment_7 = bytearray([0xE5, 0x00, 0x31, 0x9F, 0x4D, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0x43, 0x53, 0x11, 0x44, 0x66, + 0x4E, 0x92, 0xBC, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBA, 0x53, 0x1A, + 0x60, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3A, 0x64, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x11, 0x12, 0x13, 0x14, 0x15, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + + fragment_8 = bytearray([0xE5, 0x00, 0x31, 0x9F, 0x59, + 0x02, 0x00, 0x1A, 0x2A, 0x3F, 0x09, 0xAB, 0x43, + 0x60, 0x00, 0xF0, 0x00, 0x00, 0x10, 0x3A, 0x64, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x11, 0x12, 0x13, 0x14, 0x15, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x1A, 0x2A, 0x3F, 0x09, 0xAB, 0x43, + 0x80, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, 0x4E, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0x43, 0x53, 0x11, 0x44, 0x66, + 0x4E, 0x92, 0xBC, 0x53, 0x1A, 0x44, 0x66, 0x77]) + + fragment_9 = bytearray([0xE5, 0x00, 0x31, 0x9F, 0x65, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, + 0x80, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0xBB, 0x53, 0x11, 0x4C, 0x66, 0x4E, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0xa3, 0x53, 0x11, 0x44, 0x66, + 0xFE, 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x4D, 0x66, 0x77, 0x99]) + + fragment_10 = bytearray([0xE5, 0x00, 0x31, 0x9F, 0x71, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, + 0x80, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0x1B, 0x53, 0x11, 0x44, 0x66, 0x4E, + 0x22, 0xBB, 0x51, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0xD3, 0x53, 0x11, 0x44, 0x66, + 0x4E, 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A]) + + fragment_11 = bytearray([0xE5, 0x00, 0x31, 0x9F, 0x7D, + 0xC0, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0xBB, 0x53, 0x11, 0x44, 0xCC, 0x4E, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0x4A, 0x53, 0x11, 0x44, 0x66, + 0x4E, 0x92, 0xBC, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, + 0x80, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, 0x4E]) + + fragment_12 = bytearray([0xE5, 0x00, 0x31, 0x9F, 0x89, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0x43, 0x53, 0x11, 0x44, 0x66, + 0x4E, 0x92, 0xBB, 0x53, 0x3A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, + 0x80, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0xBB, 0x53, 0x11, 0x4C, 0x66, 0x4E, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA]) + + fragment_13 = bytearray([0xE5, 0x00, 0x31, 0x9F, 0x95, + 0x44, 0x54, 0x12, 0xa3, 0x53, 0x11, 0x44, 0x66, + 0xFE, 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, + 0x80, 0x00, 0xFA, 0xA5, 0x1B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0x1B, 0x53, 0x11, 0x44, 0x66, 0x4E, + 0x22, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0xD3, 0x53, 0x11, 0x44, 0x66]) + + message_info = common.MessageInfo() + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x00, 0x00, 0x00, 0x11, 0x12, 0x13, 0x14, 0x15])) + message_info.destination_mac_address = common.MacAddress.from_eui64( + bytearray([0x00, 0x00, 0x1A, 0x2A, 0x3F, 0x09, 0xAB, 0x43])) + + parser = create_default_lowpan_parser(context_manager=None) + + # WHEN + self.assertIsNone(parser.parse(io.BytesIO(fragment_4), message_info)) + self.assertIsNone(parser.parse(io.BytesIO(fragment_2), message_info)) + self.assertIsNone(parser.parse(io.BytesIO(fragment_3), message_info)) + self.assertIsNone(parser.parse(io.BytesIO(fragment_13), message_info)) + self.assertIsNone(parser.parse(io.BytesIO(fragment_5), message_info)) + self.assertIsNone(parser.parse(io.BytesIO(fragment_6), message_info)) + self.assertIsNone(parser.parse(io.BytesIO(fragment_7), message_info)) + self.assertIsNone(parser.parse(io.BytesIO(fragment_8), message_info)) + self.assertIsNone(parser.parse(io.BytesIO(fragment_9), message_info)) + self.assertIsNone(parser.parse(io.BytesIO(fragment_10), message_info)) + self.assertIsNone(parser.parse(io.BytesIO(fragment_11), message_info)) + self.assertIsNone(parser.parse(io.BytesIO(fragment_12), message_info)) + actual_ipv6_packet = parser.parse(io.BytesIO(fragment_1), message_info) + + # THEN + ipv6_packet = bytearray([0x60, 0x00, 0x00, 0x00, 0x04, 0xD8, 0x3A, 0x40, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x11, 0x12, 0x13, 0x14, 0x15, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x1A, 0x2A, 0x3F, 0x09, 0xAB, 0x43, # / * 40 * / + 0x80, 0x00, 0xAB, 0x64, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, 0x4E, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0x43, 0x53, 0x11, 0x44, 0x66, + 0x4E, 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, # / * 120 * / + 0x80, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0xBB, 0x53, 0x11, 0x4C, 0x66, 0x4E, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0xa3, 0x53, 0x11, 0x44, 0x66, + 0xFE, 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, # / * 200 * / + 0x80, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0x1B, 0x53, 0x11, 0x44, 0x66, 0x4E, + 0x22, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0xD3, 0x53, 0x11, 0x44, 0x66, + 0x4E, 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, # / * 280 * / + 0xC0, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0xBB, 0x53, 0x11, 0x44, 0xCC, 0x4E, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0x43, 0x53, 0x11, 0x44, 0x66, + 0x4E, 0x92, 0xBC, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, # / * 360 * / + 0x80, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, 0x4E, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0x43, 0x53, 0x11, 0x44, 0x66, + 0x4E, 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, + 0x80, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0xBB, 0x53, 0x11, 0x4C, 0x66, 0x4E, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0xa3, 0x53, 0x11, 0x44, 0x66, + 0xFE, 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, + 0x80, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0x1B, 0x53, 0x11, 0x44, 0x66, 0x4E, + 0x22, 0xBB, 0x53, 0x1A, 0x44, 0x67, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0xD3, 0x53, 0x11, 0x44, 0x66, + 0x4E, 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, + 0xC0, 0x00, 0xFA, 0x15, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0xBB, 0x53, 0x11, 0x44, 0xCC, 0x4E, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0x43, 0x53, 0x11, 0x44, 0x66, + 0x4E, 0x92, 0xBC, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBA, 0x53, 0x1A, + 0x60, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3A, 0x64, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x11, 0x12, 0x13, 0x14, 0x15, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x1A, 0x2A, 0x3F, 0x09, 0xAB, 0x43, # / * 720 * / + 0x60, 0x00, 0xF0, 0x00, 0x00, 0x10, 0x3A, 0x64, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x11, 0x12, 0x13, 0x14, 0x15, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x1A, 0x2A, 0x3F, 0x09, 0xAB, 0x43, + 0x80, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, 0x4E, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0x43, 0x53, 0x11, 0x44, 0x66, + 0x4E, 0x92, 0xBC, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, + 0x80, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0xBB, 0x53, 0x11, 0x4C, 0x66, 0x4E, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0xa3, 0x53, 0x11, 0x44, 0x66, + 0xFE, 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x4D, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, + 0x80, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0x1B, 0x53, 0x11, 0x44, 0x66, 0x4E, + 0x22, 0xBB, 0x51, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0xD3, 0x53, 0x11, 0x44, 0x66, + 0x4E, 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, + 0xC0, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0xBB, 0x53, 0x11, 0x44, 0xCC, 0x4E, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0x4A, 0x53, 0x11, 0x44, 0x66, + 0x4E, 0x92, 0xBC, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, # / * 1080 * / + 0x80, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, 0x4E, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0x43, 0x53, 0x11, 0x44, 0x66, + 0x4E, 0x92, 0xBB, 0x53, 0x3A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, + 0x80, 0x00, 0xFA, 0xA5, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0xBB, 0x53, 0x11, 0x4C, 0x66, 0x4E, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0xa3, 0x53, 0x11, 0x44, 0x66, + 0xFE, 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, + 0x99, 0x1A, 0x92, 0xBB, 0x53, 0x11, 0x44, 0x66, + 0x92, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x11, 0x44, 0x66, 0x4E, 0x92, 0xBB, 0x53, 0x1A, + 0x80, 0x00, 0xFA, 0xA5, 0x1B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0x1B, 0x53, 0x11, 0x44, 0x66, 0x4E, + 0x22, 0xBB, 0x53, 0x1A, 0x44, 0x66, 0x77, 0x99, + 0x15, 0xB3, 0x00, 0x54, 0xCC, 0x54, 0x01, 0xAA, + 0x44, 0x54, 0x12, 0xD3, 0x53, 0x11, 0x44, 0x66]) + self.assertEqual(ipv6_packet, actual_ipv6_packet.to_bytes()) + + def test_should_defragment_IPv6_packet_when_parse_method_is_called_with_fragments(self): + # GIVEN + message_info = common.MessageInfo() + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x00, 0x00, 0x00, 0x11, 0x12, 0x13, 0x14, 0x15])) + message_info.destination_mac_address = common.MacAddress.from_eui64( + bytearray([0x00, 0x00, 0x1A, 0x2A, 0x3F, 0x09, 0xAB, 0x43])) + + fragment_1 = bytearray([0xC0, 0x38, 0x12, 0x34, 0x7A, 0x33, 0x3A, 0x80, + 0x00, 0x1A, 0x33, 0x0B, 0xC0, 0x00, 0x04]) + + fragment_2 = bytearray([0xE0, 0x38, 0x12, 0x34, 0x06, 0x4E, 0x92, 0xBB, + 0x53, 0x11, 0x12, 0x13, 0x14]) + + parser = create_default_lowpan_parser(None) + + # WHEN + self.assertIsNone(parser.parse(io.BytesIO(fragment_1), message_info=message_info)) + actual_ipv6_packet = parser.parse(io.BytesIO(fragment_2), message_info=message_info) + + # THEN + ipv6_packet = bytearray([0x60, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3A, 0x40, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x11, 0x12, 0x13, 0x14, 0x15, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x1A, 0x2A, 0x3F, 0x09, 0xAB, 0x43, + 0x80, 0x00, 0x1A, 0x33, 0x0B, 0xC0, 0x00, 0x04, + 0x4E, 0x92, 0xBB, 0x53, 0x11, 0x12, 0x13, 0x14]) + self.assertEqual(ipv6_packet, actual_ipv6_packet.to_bytes()) + + +class TestLowpanUdpHeaderFactory(unittest.TestCase): + + def test_should_parse_udp_datagram_ports_when_decompress_udp_ports_method_is_called_with_udphc_p_equal_0(self): + # GIVEN + factory = lowpan.LowpanUdpHeaderFactory() + + p = factory.UDP_HC_P_BOTH_FULL + + udphc = lowpan.LowpanUDPHC(any_c(), p) + + src_port = any_src_port() + dst_port = any_dst_port() + + data_bytes = struct.pack(">H", src_port) + struct.pack(">H", dst_port) + + # WHEN + actual_src_port, actual_dst_port = factory._decompress_udp_ports(udphc, io.BytesIO(data_bytes)) + + # THEN + self.assertEqual(src_port, actual_src_port) + self.assertEqual(dst_port, actual_dst_port) + self.assertEqual(0, p) + + def test_should_parse_udp_datagram_ports_when_decompress_udp_ports_method_is_called_with_udphc_p_equal_1(self): + # GIVEN + factory = lowpan.LowpanUdpHeaderFactory() + + p = factory.UDP_HC_P_DST_COMPR + + udphc = lowpan.LowpanUDPHC(any_c(), p) + + src_port = any_src_port() + dst_port = any_compressable_dst_port() + + data_bytes = struct.pack(">H", src_port) + struct.pack(">H", dst_port)[1] + + # WHEN + actual_src_port, actual_dst_port = factory._decompress_udp_ports(udphc, io.BytesIO(data_bytes)) + + # THEN + self.assertEqual(1, p) + self.assertEqual(src_port, actual_src_port) + self.assertEqual(dst_port, actual_dst_port) + + def test_should_parse_udp_datagram_ports_when_decompress_udp_ports_method_is_called_with_udphc_p_equal_2(self): + # GIVEN + factory = lowpan.LowpanUdpHeaderFactory() + + p = factory.UDP_HC_P_SRC_COMPR + + udphc = lowpan.LowpanUDPHC(any_c(), p) + + src_port = any_compressable_src_port() + dst_port = any_dst_port() + + data_bytes = struct.pack(">H", src_port)[1] + struct.pack(">H", dst_port) + + # WHEN + actual_src_port, actual_dst_port = factory._decompress_udp_ports(udphc, io.BytesIO(data_bytes)) + + # THEN + self.assertEqual(2, p) + self.assertEqual(src_port, actual_src_port) + self.assertEqual(dst_port, actual_dst_port) + + def test_should_parse_udp_datagram_ports_when_decompress_udp_ports_method_is_called_with_udphc_p_equal_3(self): + # GIVEN + factory = lowpan.LowpanUdpHeaderFactory() + + p = factory.UDP_HC_P_BOTH_COMPR + + udphc = lowpan.LowpanUDPHC(any_c(), p) + + src_port = any_nibble_src_port() + dst_port = any_nibble_dst_port() + + data_bytes = bytearray([((src_port & 0x0F) << 4) | (dst_port & 0x0F)]) + + # WHEN + actual_src_port, actual_dst_port = factory._decompress_udp_ports(udphc, io.BytesIO(data_bytes)) + + # THEN + self.assertEqual(3, p) + self.assertEqual(src_port, actual_src_port) + self.assertEqual(dst_port, actual_dst_port) + + def test_should_parse_udp_datagram_checksum_when_decompress_udp_checksum_is_called_with_udphc_c_equal_0(self): + # GIVEN + factory = lowpan.LowpanUdpHeaderFactory() + + c = factory.UDP_HC_C_INLINE + + udphc = lowpan.LowpanUDPHC(c, any_p()) + + checksum = any_checksum() + + data_bytes = struct.pack(">H", checksum) + + # WHEN + actual_checksum = factory._decompress_udp_checksum(udphc, io.BytesIO(data_bytes)) + + # THEN + self.assertEqual(0, c) + self.assertEqual(checksum, actual_checksum) + + def test_should_parse_udp_datagram_checksum_when_decompress_udp_checksum_is_called_with_udphc_c_equal_1(self): + # GIVEN + factory = lowpan.LowpanUdpHeaderFactory() + + c = factory.UDP_HC_C_ELIDED + + udphc = lowpan.LowpanUDPHC(c, any_p()) + + data_bytes = bytearray() + + # WHEN + actual_checksum = factory._decompress_udp_checksum(udphc, io.BytesIO(data_bytes)) + + # THEN + self.assertEqual(1, c) + self.assertEqual(0, actual_checksum) + + +class TestLowpanIpv6HeaderFactory(unittest.TestCase): + + IPV6_LINKLOCAL_PREFIX = bytearray([0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + + def test_should_parse_traffic_class_and_flow_label_when_decompress_tf_method_is_called_with_iphc_tf_equal_0(self): + # GIVEN + ecn = any_ecn() + dscp = any_dscp() + flow_label = any_flow_label() + + data_bytes = bytearray() + data_bytes.append((ecn << 6) | dscp) + data_bytes.append((flow_label >> 16) & 0x0F) + data_bytes.append((flow_label >> 8) & 0xFF) + data_bytes.append(flow_label & 0xFF) + + factory = lowpan.LowpanIpv6HeaderFactory() + + tf = factory.IPHC_TF_4B + iphc = lowpan.LowpanIPHC(tf, any_nh(), any_hlim(), any_cid(), any_sac(), + any_sam(), any_m(), any_dac(), any_dam()) + + # WHEN + actual_traffic_class, actual_flow_label = factory._decompress_tf(iphc, io.BytesIO(data_bytes)) + + # THEN + self.assertEqual(0, tf) + self.assertEqual((dscp << 2) | ecn, actual_traffic_class) + self.assertEqual(flow_label, actual_flow_label) + + def test_should_parse_traffic_class_and_flow_label_when_decompress_tf_method_is_called_with_iphc_tf_equal_1(self): + # GIVEN + ecn = any_ecn() + flow_label = any_flow_label() + + data_bytes = bytearray() + data_bytes.append((ecn << 6) | (flow_label >> 16) & 0x0F) + data_bytes.append((flow_label >> 8) & 0xFF) + data_bytes.append(flow_label & 0xFF) + + factory = lowpan.LowpanIpv6HeaderFactory() + + tf = factory.IPHC_TF_3B + iphc = lowpan.LowpanIPHC(tf, any_nh(), any_hlim(), any_cid(), any_sac(), + any_sam(), any_m(), any_dac(), any_dam()) + + # WHEN + actual_traffic_class, actual_flow_label = factory._decompress_tf(iphc, io.BytesIO(data_bytes)) + + # THEN + self.assertEqual(1, tf) + self.assertEqual(ecn, actual_traffic_class) + self.assertEqual(flow_label, actual_flow_label) + + def test_should_parse_traffic_class_and_flow_label_when_decompress_tf_method_is_called_with_iphc_tf_equal_2(self): + # GIVEN + ecn = any_ecn() + dscp = any_dscp() + flow_label = any_flow_label() + + data_bytes = bytearray([(ecn << 6) | dscp]) + + factory = lowpan.LowpanIpv6HeaderFactory() + + tf = factory.IPHC_TF_1B + iphc = lowpan.LowpanIPHC(tf, any_nh(), any_hlim(), any_cid(), any_sac(), + any_sam(), any_m(), any_dac(), any_dam()) + + # WHEN + actual_traffic_class, actual_flow_label = factory._decompress_tf(iphc, io.BytesIO(data_bytes)) + + # THEN + self.assertEqual(2, tf) + self.assertEqual((dscp << 2) | ecn, actual_traffic_class) + self.assertEqual(0, actual_flow_label) + + def test_should_parse_traffic_class_and_flow_label_when_decompress_tf_method_is_called_with_iphc_tf_equal_3(self): + # GIVEN + flow_label = any_flow_label() + + data_bytes = bytearray() + + factory = lowpan.LowpanIpv6HeaderFactory() + + tf = factory.IPHC_TF_ELIDED + iphc = lowpan.LowpanIPHC(tf, any_nh(), any_hlim(), any_cid(), any_sac(), + any_sam(), any_m(), any_dac(), any_dam()) + + # WHEN + actual_traffic_class, actual_flow_label = factory._decompress_tf(iphc, io.BytesIO(data_bytes)) + + # THEN + self.assertEqual(3, tf) + self.assertEqual(0, actual_traffic_class) + self.assertEqual(0, actual_flow_label) + + def test_should_parse_traffic_class_and_flow_label_when_decompress_nh_method_is_called_with_iphc_nh_equal_0(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + next_header = any_next_header() + + nh = factory.IPHC_NH_INLINE + iphc = lowpan.LowpanIPHC(any_tf(), nh, any_hlim(), any_cid(), any_sac(), + any_sam(), any_m(), any_dac(), any_dam()) + + data_bytes = bytearray([next_header]) + + # WHEN + actual_next_header = factory._decompress_nh(iphc, io.BytesIO(data_bytes)) + + # THEN + self.assertEqual(0, nh) + self.assertEqual(next_header, actual_next_header) + + def test_should_parse_traffic_class_and_flow_label_when_decompress_nh_method_is_called_with_iphc_nh_equal_1(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + nh = factory.IPHC_NH_COMPRESSED + iphc = lowpan.LowpanIPHC(any_tf(), nh, any_hlim(), any_cid(), any_sac(), + any_sam(), any_m(), any_dac(), any_dam()) + + data_bytes = bytearray() + + # WHEN + actual_next_header = factory._decompress_nh(iphc, io.BytesIO(data_bytes)) + + # THEN + self.assertEqual(1, nh) + self.assertEqual(None, actual_next_header) + + def test_should_parse_hop_limit_when_decompress_hlim_is_called_with_iphc_hlim_equal_0(self): + # GIVEN + hop_limit = any_hop_limit() + + factory = lowpan.LowpanIpv6HeaderFactory() + + hlim = factory.IPHC_HLIM_INLINE + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), hlim, any_cid(), any_sac(), + any_sam(), any_m(), any_dac(), any_dam()) + + data_bytes = bytearray([hop_limit]) + + # WHEN + actual_hop_limit = factory._decompress_hlim(iphc, io.BytesIO(data_bytes)) + + # THEN + self.assertEqual(0, hlim) + self.assertEqual(hop_limit, actual_hop_limit) + + def test_should_parse_hop_limit_when_decompress_hlim_is_called_with_iphc_hlim_equal_1(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + hlim = factory.IPHC_HLIM_1 + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), hlim, any_cid(), any_sac(), + any_sam(), any_m(), any_dac(), any_dam()) + + data_bytes = bytearray() + + # WHEN + actual_hop_limit = factory._decompress_hlim(iphc, io.BytesIO(data_bytes)) + + # THEN + self.assertEqual(1, hlim) + self.assertEqual(1, actual_hop_limit) + + def test_should_parse_hop_limit_when_decompress_hlim_is_called_with_iphc_hlim_equal_2(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + hlim = factory.IPHC_HLIM_64 + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), hlim, any_cid(), any_sac(), + any_sam(), any_m(), any_dac(), any_dam()) + + data_bytes = bytearray() + + # WHEN + actual_hop_limit = factory._decompress_hlim(iphc, io.BytesIO(data_bytes)) + + # THEN + self.assertEqual(2, hlim) + self.assertEqual(64, actual_hop_limit) + + def test_should_parse_hop_limit_when_decompress_hlim_is_called_with_iphc_hlim_equal_3(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + hlim = factory.IPHC_HLIM_255 + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), hlim, any_cid(), any_sac(), + any_sam(), any_m(), any_dac(), any_dam()) + + data_bytes = bytearray() + + # WHEN + actual_hop_limit = factory._decompress_hlim(iphc, io.BytesIO(data_bytes)) + + # THEN + self.assertEqual(3, hlim) + self.assertEqual(255, actual_hop_limit) + + def test_should_parse_source_address_when_decompress_src_addr_is_called_with_sac_equal_0_and_sam_equal_0(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + src_addr = any_src_addr() + + sac = factory.IPHC_SAC_STATELESS + sam = factory.IPHC_SAM_128B + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), sac, + sam, any_m(), any_dac(), any_dam()) + + # WHEN + actual_src_addr = factory._decompress_src_addr(iphc, any_src_mac_addr(), any_sci(), io.BytesIO(src_addr)) + + # THEN + self.assertEqual(0, sac) + self.assertEqual(0, sam) + self.assertEqual(bytes(src_addr), actual_src_addr) + + def test_should_parse_source_address_when_decompress_src_addr_is_called_with_sac_equal_0_and_sam_equal_1(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + eui64 = any_eui64() + + sac = factory.IPHC_SAC_STATELESS + sam = factory.IPHC_SAM_64B + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), sac, + sam, any_m(), any_dac(), any_dam()) + + # WHEN + actual_src_addr = factory._decompress_src_addr(iphc, any_src_mac_addr(), any_sci(), io.BytesIO(eui64)) + + # THEN + self.assertEqual(0, sac) + self.assertEqual(1, sam) + self.assertEqual(self.IPV6_LINKLOCAL_PREFIX + eui64, actual_src_addr) + + def test_should_parse_source_address_when_decompress_src_addr_is_called_with_sac_equal_0_and_sam_equal_2(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + rloc16 = any_rloc16() + + sac = factory.IPHC_SAC_STATELESS + sam = factory.IPHC_SAM_16B + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), sac, + sam, any_m(), any_dac(), any_dam()) + + # WHEN + actual_src_addr = factory._decompress_src_addr(iphc, any_src_mac_addr(), any_sci(), io.BytesIO(rloc16)) + + # THEN + self.assertEqual(0, sac) + self.assertEqual(2, sam) + self.assertEqual(self.IPV6_LINKLOCAL_PREFIX + + bytearray([0x00, 0x00, 0x00, 0xff, 0xfe, 0x00]) + rloc16, actual_src_addr) + + def test_should_parse_source_address_when_decompress_src_addr_is_called_with_sac_equal_0_and_sam_equal_3(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + src_mac_addr = common.MacAddress.from_eui64(any_src_mac_addr()) + + sac = factory.IPHC_SAC_STATELESS + sam = factory.IPHC_SAM_ELIDED + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), sac, + sam, any_m(), any_dac(), any_dam()) + + data_bytes = bytearray([]) + + # WHEN + actual_src_addr = factory._decompress_src_addr(iphc, src_mac_addr, any_sci(), io.BytesIO(data_bytes)) + + # THEN + self.assertEqual(0, sac) + self.assertEqual(3, sam) + self.assertEqual(self.IPV6_LINKLOCAL_PREFIX + + bytearray([src_mac_addr.mac_address[0] ^ 0x02]) + + src_mac_addr.mac_address[1:], actual_src_addr) + + def _merge_prefix_and_address(self, prefix, prefix_length, address): + total_bytes = 16 + + prefix_length_in_bytes = prefix_length / 8 + + if (prefix_length_in_bytes + len(address)) > total_bytes: + total_bytes -= prefix_length_in_bytes + + return prefix[:prefix_length_in_bytes] + address[-total_bytes:] + + else: + total_bytes -= prefix_length_in_bytes + total_bytes -= len(address) + + return prefix[:prefix_length_in_bytes] + bytearray([0x00] * total_bytes) + address + + def test_should_parse_source_address_when_decompress_src_addr_is_called_with_sac_equal_1_and_sam_equal_0(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory(None) + + src_addr = any_src_addr() + + sac = factory.IPHC_SAC_STATEFUL + sam = factory.IPHC_SAM_UNSPECIFIED + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), sac, + sam, any_m(), any_dac(), any_dam()) + + # WHEN + actual_src_addr = factory._decompress_src_addr(iphc, any_src_mac_addr(), any_sci(), io.BytesIO(src_addr)) + + # THEN + self.assertEqual(1, sac) + self.assertEqual(0, sam) + self.assertEqual(bytearray([0x00] * 16), actual_src_addr) + + def test_should_parse_source_address_when_decompress_src_addr_is_called_with_sac_equal_1_and_sam_equal_1(self): + # GIVEN + sci = any_sci() + + context = any_context() + + context_manager = lowpan.ContextManager() + context_manager[sci] = context + + factory = lowpan.LowpanIpv6HeaderFactory(context_manager) + + eui64 = any_eui64() + + sac = factory.IPHC_SAC_STATEFUL + sam = factory.IPHC_SAM_64B + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), sac, + sam, any_m(), any_dac(), any_dam()) + + src_addr = self._merge_prefix_and_address(context.prefix, context.prefix_length, eui64) + + # WHEN + actual_src_addr = factory._decompress_src_addr(iphc, any_src_mac_addr(), sci, io.BytesIO(eui64)) + + # THEN + self.assertEqual(1, sac) + self.assertEqual(1, sam) + self.assertEqual(src_addr, actual_src_addr) + + def test_should_parse_source_address_when_decompress_src_addr_is_called_with_sac_equal_1_and_sam_equal_2(self): + # GIVEN + sci = any_sci() + + context = any_context() + + context_manager = lowpan.ContextManager() + context_manager[sci] = context + + factory = lowpan.LowpanIpv6HeaderFactory(context_manager) + + rloc16 = any_rloc16() + + sac = factory.IPHC_SAC_STATEFUL + sam = factory.IPHC_SAM_16B + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), sac, + sam, any_m(), any_dac(), any_dam()) + + iid = bytearray([0x00, 0x00, 0x00, 0xff, 0xfe, 0x00]) + rloc16 + + src_addr = self._merge_prefix_and_address(context.prefix, context.prefix_length, iid) + + # WHEN + actual_src_addr = factory._decompress_src_addr(iphc, any_src_mac_addr(), sci, io.BytesIO(rloc16)) + + # THEN + self.assertEqual(1, sac) + self.assertEqual(2, sam) + self.assertEqual(src_addr, actual_src_addr) + + def test_should_parse_source_address_when_decompress_src_addr_is_called_with_sac_equal_1_and_sam_equal_3(self): + # GIVEN + sci = any_sci() + + context = any_context() + + context_manager = lowpan.ContextManager() + context_manager[sci] = context + + factory = lowpan.LowpanIpv6HeaderFactory(context_manager) + + src_mac_addr = common.MacAddress.from_eui64(any_src_mac_addr()) + + sac = factory.IPHC_SAC_STATEFUL + sam = factory.IPHC_SAM_0B + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), sac, + sam, any_m(), any_dac(), any_dam()) + + iid = bytearray([src_mac_addr.mac_address[0] ^ 0x02]) + src_mac_addr.mac_address[1:] + + src_addr = self._merge_prefix_and_address(context.prefix, context.prefix_length, iid) + + data_bytes = bytearray([]) + + # WHEN + actual_src_addr = factory._decompress_src_addr(iphc, src_mac_addr, sci, io.BytesIO(data_bytes)) + + # THEN + self.assertEqual(1, sac) + self.assertEqual(3, sam) + self.assertEqual(src_addr, actual_src_addr) + + def test_should_parse_destination_address_when_decompress_dst_addr_is_called_with_m_equal_0_and_dac_equal_0_and_dam_equal_0(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + ipv6_addr = any_dst_addr() + + m = factory.IPHC_M_NO + dac = factory.IPHC_DAC_STATELESS + dam = factory.IPHC_DAM_128B + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), any_sac(), + any_sam(), m, dac, dam) + + dst_mac_addr = bytearray([0x00] * 8) + + # WHEN + actual_dst_addr = factory._decompress_dst_addr(iphc, dst_mac_addr, any_dci(), io.BytesIO(ipv6_addr)) + + # THEN + self.assertEqual(0, m) + self.assertEqual(0, dac) + self.assertEqual(0, dam) + self.assertEqual(ipv6_addr, actual_dst_addr) + + def test_should_parse_destination_address_when_decompress_dst_addr_is_called_with_m_equal_0_and_dac_equal_0_and_dam_equal_1(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + eui64 = any_eui64() + + m = factory.IPHC_M_NO + dac = factory.IPHC_DAC_STATELESS + dam = factory.IPHC_DAM_64B + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), any_sac(), + any_sam(), m, dac, dam) + + # WHEN + actual_dst_addr = factory._decompress_dst_addr(iphc, any_dst_mac_addr(), any_dci(), io.BytesIO(eui64)) + + # THEN + self.assertEqual(0, m) + self.assertEqual(0, dac) + self.assertEqual(1, dam) + self.assertEqual(self.IPV6_LINKLOCAL_PREFIX + eui64, actual_dst_addr) + + def test_should_parse_destination_address_when_decompress_dst_addr_is_called_with_m_equal_0_and_dac_equal_0_and_dam_equal_2(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + rloc16 = any_rloc16() + + m = factory.IPHC_M_NO + dac = factory.IPHC_DAC_STATELESS + dam = factory.IPHC_DAM_16B + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), any_sac(), + any_sam(), m, dac, dam) + + # WHEN + actual_dst_addr = factory._decompress_dst_addr(iphc, any_dst_mac_addr(), any_dci(), io.BytesIO(rloc16)) + + # THEN + self.assertEqual(0, m) + self.assertEqual(0, dac) + self.assertEqual(2, dam) + self.assertEqual(self.IPV6_LINKLOCAL_PREFIX + + bytearray([0x00, 0x00, 0x00, 0xff, 0xfe, 0x00]) + rloc16, actual_dst_addr) + + def test_should_parse_destination_address_when_decompress_dst_addr_is_called_with_m_equal_0_and_dac_equal_0_and_dam_equal_3(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + dst_mac_addr = common.MacAddress.from_eui64(any_dst_mac_addr()) + + m = factory.IPHC_M_NO + dac = factory.IPHC_DAC_STATELESS + dam = factory.IPHC_DAM_ELIDED + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), any_sac(), + any_sam(), m, dac, dam) + + data_bytes = bytearray([]) + + # WHEN + actual_dst_addr = factory._decompress_dst_addr(iphc, dst_mac_addr, any_dci(), io.BytesIO(data_bytes)) + + # THEN + self.assertEqual(0, m) + self.assertEqual(0, dac) + self.assertEqual(3, dam) + self.assertEqual(self.IPV6_LINKLOCAL_PREFIX + + bytearray([dst_mac_addr.mac_address[0] ^ 0x02]) + + dst_mac_addr.mac_address[1:], actual_dst_addr) + + def test_should_raise_RuntimeError_when_decompress_dst_addr_is_called_with_m_equal_0_and_dac_equal_1_and_dam_equal_0(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + ipv6_addr = any_dst_addr() + + m = factory.IPHC_M_NO + dac = factory.IPHC_DAC_STATEFUL + dam = factory.IPHC_DAM_128B + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), any_sac(), + any_sam(), m, dac, dam) + + # WHEN + self.assertRaises(RuntimeError, factory._decompress_dst_addr, iphc, + any_dst_mac_addr(), any_dci(), io.BytesIO(ipv6_addr)) + + def test_should_parse_destination_address_when_decompress_dst_addr_is_called_with_m_equal_0_and_dac_equal_1_and_dam_equal_1(self): + # GIVEN + dci = any_dci() + + context = any_context() + + context_manager = lowpan.ContextManager() + context_manager[dci] = context + + factory = lowpan.LowpanIpv6HeaderFactory(context_manager) + + eui64 = any_eui64() + + m = factory.IPHC_M_NO + dac = factory.IPHC_DAC_STATEFUL + dam = factory.IPHC_DAM_64B + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), any_sac(), + any_sam(), m, dac, dam) + + dst_addr = self._merge_prefix_and_address(context.prefix, context.prefix_length, eui64) + + # WHEN + actual_dst_addr = factory._decompress_dst_addr(iphc, any_dst_mac_addr(), dci, io.BytesIO(eui64)) + + # THEN + self.assertEqual(0, m) + self.assertEqual(1, dac) + self.assertEqual(1, dam) + self.assertEqual(dst_addr, actual_dst_addr) + + def test_should_parse_destination_address_when_decompress_dst_addr_is_called_with_m_equal_0_and_dac_equal_1_and_dam_equal_2(self): + # GIVEN + dci = any_dci() + + context = any_context() + + context_manager = lowpan.ContextManager() + context_manager[dci] = context + + factory = lowpan.LowpanIpv6HeaderFactory(context_manager) + + rloc16 = any_rloc16() + + m = factory.IPHC_M_NO + dac = factory.IPHC_DAC_STATEFUL + dam = factory.IPHC_DAM_16B + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), any_sac(), + any_sam(), m, dac, dam) + + iid = bytearray([0x00, 0x00, 0x00, 0xff, 0xfe, 0x00]) + rloc16 + + dst_addr = self._merge_prefix_and_address(context.prefix, context.prefix_length, iid) + + # WHEN + actual_dst_addr = factory._decompress_dst_addr(iphc, any_dst_mac_addr(), dci, io.BytesIO(rloc16)) + + # THEN + self.assertEqual(0, m) + self.assertEqual(1, dac) + self.assertEqual(2, dam) + self.assertEqual(dst_addr, actual_dst_addr) + + def test_should_parse_destination_address_when_decompress_dst_addr_is_called_with_m_equal_0_and_dac_equal_1_and_dam_equal_3(self): + # GIVEN + dci = any_dci() + + context = any_context() + + context_manager = lowpan.ContextManager() + context_manager[dci] = context + + factory = lowpan.LowpanIpv6HeaderFactory(context_manager) + + dst_mac_addr = common.MacAddress.from_eui64(any_dst_mac_addr()) + + m = factory.IPHC_M_NO + dac = factory.IPHC_DAC_STATEFUL + dam = factory.IPHC_DAM_0B + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), any_sac(), + any_sam(), m, dac, dam) + + iid = bytearray([dst_mac_addr.mac_address[0] ^ 0x02]) + dst_mac_addr.mac_address[1:] + + dst_addr = self._merge_prefix_and_address(context.prefix, context.prefix_length, iid) + + data_bytes = bytearray([]) + + # WHEN + actual_dst_addr = factory._decompress_dst_addr(iphc, dst_mac_addr, dci, io.BytesIO(data_bytes)) + + # THEN + self.assertEqual(0, m) + self.assertEqual(1, dac) + self.assertEqual(3, dam) + self.assertEqual(dst_addr, actual_dst_addr) + + def test_should_parse_destination_address_when_decompress_dst_addr_is_called_with_m_equal_1_and_dac_equal_0_and_dam_equal_0(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + ipv6_addr = any_dst_addr() + + m = factory.IPHC_M_YES + dac = factory.IPHC_DAC_STATELESS + dam = factory.IPHC_DAM_128B + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), any_sac(), + any_sam(), m, dac, dam) + + # WHEN + actual_dst_addr = factory._decompress_dst_addr(iphc, any_dst_mac_addr(), any_dci(), io.BytesIO(ipv6_addr)) + + # THEN + self.assertEqual(1, m) + self.assertEqual(0, dac) + self.assertEqual(0, dam) + self.assertEqual(ipv6_addr, actual_dst_addr) + + def test_should_parse_destination_address_when_decompress_dst_addr_is_called_with_m_equal_1_and_dac_equal_0_and_dam_equal_1(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + addr48b = any_48bits_addr() + + m = factory.IPHC_M_YES + dac = factory.IPHC_DAC_STATELESS + dam = factory.IPHC_DAM_48B + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), any_sac(), + any_sam(), m, dac, dam) + + expected_dst_addr = bytearray([0xff, addr48b[0], 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, addr48b[1], addr48b[2], addr48b[3], addr48b[4], addr48b[5]]) + + # WHEN + actual_dst_addr = factory._decompress_dst_addr(iphc, any_dst_mac_addr(), any_dci(), io.BytesIO(addr48b)) + + # THEN + self.assertEqual(1, m) + self.assertEqual(0, dac) + self.assertEqual(1, dam) + self.assertEqual(expected_dst_addr, actual_dst_addr) + + def test_should_parse_destination_address_when_decompress_dst_addr_is_called_with_m_equal_1_and_dac_equal_0_and_dam_equal_2(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + addr32b = any_32bits_addr() + + m = factory.IPHC_M_YES + dac = factory.IPHC_DAC_STATELESS + dam = factory.IPHC_DAM_32B + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), any_sac(), + any_sam(), m, dac, dam) + + expected_dst_addr = bytearray([0xff, addr32b[0], 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, addr32b[1], addr32b[2], addr32b[3]]) + + # WHEN + actual_dst_addr = factory._decompress_dst_addr(iphc, any_dst_mac_addr(), any_dci(), io.BytesIO(addr32b)) + + # THEN + self.assertEqual(1, m) + self.assertEqual(0, dac) + self.assertEqual(2, dam) + self.assertEqual(expected_dst_addr, actual_dst_addr) + + def test_should_parse_destination_address_when_decompress_dst_addr_is_called_with_m_equal_1_and_dac_equal_0_and_dam_equal_3(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + addr8b = any_8bits_addr() + + m = factory.IPHC_M_YES + dac = factory.IPHC_DAC_STATELESS + dam = factory.IPHC_DAM_8B + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), any_sac(), + any_sam(), m, dac, dam) + + expected_dst_addr = bytearray([0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, addr8b[0]]) + + # WHEN + actual_dst_addr = factory._decompress_dst_addr(iphc, any_dst_mac_addr(), any_dci(), io.BytesIO(addr8b)) + + # THEN + self.assertEqual(1, m) + self.assertEqual(0, dac) + self.assertEqual(3, dam) + self.assertEqual(expected_dst_addr, actual_dst_addr) + + def test_should_raise_RuntimeError_when_decompress_dst_addr_is_called_with_m_equal_1_and_dac_equal_1_and_dam_equal_0(self): + # GIVEN + dci = any_dci() + + context = any_context() + + context_manager = lowpan.ContextManager() + context_manager[dci] = context + + factory = lowpan.LowpanIpv6HeaderFactory(context_manager) + + addr48b = any_48bits_addr() + + m = factory.IPHC_M_YES + dac = factory.IPHC_DAC_STATEFUL + dam = factory.IPHC_DAM_128B + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), any_sac(), + any_sam(), m, dac, dam) + + prefix = context.prefix[:8] + + if len(prefix) < 8: + missing_bytes_count = 8 - len(prefix) + prefix += bytearray([0x00] * missing_bytes_count) + + prefix_length = context.prefix_length + + dst_addr = bytearray([0xff]) + addr48b[:2] + bytearray([prefix_length]) + prefix + addr48b[2:] + + # WHEN + actual_dst_addr = factory._decompress_dst_addr(iphc, any_dst_mac_addr(), dci, io.BytesIO(addr48b)) + + # THEN + self.assertEqual(1, m) + self.assertEqual(1, dac) + self.assertEqual(0, dam) + self.assertEqual(dst_addr, actual_dst_addr) + + def test_should_raise_RuntimeError_when_decompress_dst_addr_is_called_with_m_equal_1_and_dac_equal_1_and_dam_equal_1(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + addr48b = any_48bits_addr() + + m = factory.IPHC_M_YES + dac = factory.IPHC_DAC_STATEFUL + dam = factory.IPHC_DAM_48B + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), any_sac(), + any_sam(), m, dac, dam) + + expected_dst_addr = bytearray([0xff, addr48b[0], 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, addr48b[1], addr48b[2], addr48b[3], addr48b[4], addr48b[5]]) + + # WHEN + self.assertRaises(RuntimeError, factory._decompress_dst_addr, iphc, + any_dst_mac_addr(), any_dci(), io.BytesIO(addr48b)) + + def test_should_raise_RuntimeError_when_decompress_dst_addr_is_called_with_m_equal_1_and_dac_equal_1_and_dam_equal_2(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + addr32b = any_32bits_addr() + + m = factory.IPHC_M_YES + dac = factory.IPHC_DAC_STATEFUL + dam = factory.IPHC_DAM_32B + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), any_sac(), + any_sam(), m, dac, dam) + + expected_dst_addr = bytearray([0xff, addr32b[0], 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, addr32b[1], addr32b[2], addr32b[3]]) + + # WHEN + self.assertRaises(RuntimeError, factory._decompress_dst_addr, iphc, + any_dst_mac_addr(), any_dci(), io.BytesIO(addr32b)) + + def test_should_parse_destination_address_when_decompress_dst_addr_is_called_with_m_equal_1_and_dac_equal_1_and_dam_equal_3(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + addr8b = any_8bits_addr() + + m = factory.IPHC_M_YES + dac = factory.IPHC_DAC_STATEFUL + dam = factory.IPHC_DAM_8B + + iphc = lowpan.LowpanIPHC(any_tf(), any_nh(), any_hlim(), any_cid(), any_sac(), + any_sam(), m, dac, dam) + + expected_dst_addr = bytearray([0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, addr8b[0]]) + + # WHEN + self.assertRaises(RuntimeError, factory._decompress_dst_addr, iphc, + any_dst_mac_addr(), any_dci(), io.BytesIO(addr8b)) + + def test_should_merge_prefix_with_address_bytes_when_merge_method_is_called_with_prefix_shorter_than_missing_bits(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + prefix = bytearray([0x20, 0x00, 0x0d, 0xb8]) + prefix_length = 32 + + address_bytes = bytearray([0x1a, 0x2b, 0x3c, 0x4d, 0x5e, 0x6f, 0x70, 0x81]) + + addr = prefix + bytearray([0x00] * 4) + address_bytes + + # WHEN + actual_addr = factory._merge_prefix_with_address(prefix, prefix_length, address_bytes) + + # THEN + self.assertEqual(addr, actual_addr) + + def test_should_merge_prefix_with_address_bytes_when_merge_method_is_called_with_prefix_longer_than_missing_bits_overlapping(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + prefix = bytearray([0x20, 0x00, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x22]) + prefix_length = 68 + + address_bytes = bytearray([0x1a, 0x2b, 0x3c, 0x4d, 0x5e, 0x6f, 0x70, 0x81]) + + addr = prefix[:-1] + bytearray([0x2a]) + address_bytes[1:] + + # WHEN + actual_addr = factory._merge_prefix_with_address(prefix, prefix_length, address_bytes) + + # THEN + self.assertEqual(addr, actual_addr) + + def test_should_merge_prefix_with_address_bytes_when_merge_method_is_called_with_prefix_longer_than_missing_bits(self): + # GIVEN + factory = lowpan.LowpanIpv6HeaderFactory() + + prefix = bytearray([0x20, 0x00, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x11, 0x01, 0x11, 0x01, 0x22]) + prefix_length = 128 + + address_bytes = bytearray([0x1a, 0x2b, 0x3c, 0x4d, 0x5e, 0x6f, 0x70, 0x81]) + + addr = prefix + + # WHEN + actual_addr = factory._merge_prefix_with_address(prefix, prefix_length, address_bytes) + + # THEN + self.assertEqual(addr, actual_addr) + + +class TestContext(unittest.TestCase): + + def test_should_extract_context_from_str_representation_when_constructor_is_called(self): + # GIVEN + prefix = "2000:db8::/64" + + # WHEN + c = lowpan.Context(prefix) + + # THEN + self.assertEqual(bytearray([0x20, 0x00, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00]), c.prefix) + self.assertEqual(64, c.prefix_length) + self.assertEqual(8, c.prefix_length_full_bytes) + + def test_should_extract_context_from_bytearray_when_construct_is_called(self): + # GIVEN + prefix = bytearray([0x20, 0x00, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00]) + + # WHEN + c = lowpan.Context(prefix) + + # THEN + self.assertEqual(bytearray([0x20, 0x00, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00]), c.prefix) + self.assertEqual(8, c.prefix_length_full_bytes) + self.assertEqual(64, c.prefix_length) + + +class TestContextManager(unittest.TestCase): + + def test_should_raise_IndexError_when_index_is_larger_than_15(self): + # GIVEN + context_manager = lowpan.ContextManager() + + index = random.randint(16, 255) + + # WHEN + with self.assertRaises(IndexError): + context_manager[index] = any_context() + + def test_should_raise_IndexError_when_index_is_smaller_than_0(self): + # GIVEN + context_manager = lowpan.ContextManager() + + index = random.randint(-255, -1) + + # WHEN + with self.assertRaises(IndexError): + context_manager[index] = any_context() + + def test_should_raise_TypeError_when_set_value_is_not_Context(self): + # GIVEN + context_manager = lowpan.ContextManager() + + # WHEN + with self.assertRaises(TypeError): + context_manager[0] = int + + +class TestLowpanMeshHeader(unittest.TestCase): + + def test_should_return_hops_left_value_when_hops_left_property_is_called(self): + # GIVEN + hops_left = any_hops_left() + + mesh_header = lowpan.LowpanMeshHeader(hops_left, any_mac_address(), any_mac_address()) + + # WHEN + actual_hops_left = mesh_header.hops_left + + # THEN + self.assertEqual(hops_left, actual_hops_left) + + def test_should_return_originator_address_value_when_originator_address_property_is_called(self): + # GIVEN + originator_address = any_mac_address() + + mesh_header = lowpan.LowpanMeshHeader(any_hops_left(), originator_address, any_mac_address()) + + # WHEN + actual_originator_address = mesh_header.originator_address + + # THEN + self.assertEqual(originator_address, actual_originator_address) + + def test_should_return_final_destination_address_value_when_final_destination_address_property_is_called(self): + # GIVEN + final_destination_address = any_mac_address() + + mesh_header = lowpan.LowpanMeshHeader(any_hops_left(), any_mac_address(), final_destination_address) + + # WHEN + actual_final_destination_address = mesh_header.final_destination_address + + # THEN + self.assertEqual(final_destination_address, actual_final_destination_address) + + +class TestLowpanMeshHeaderFactory(unittest.TestCase): + + def test_should_create_LowpanMeshHeader_when_parse_method_is_called(self): + # GIVEN + hops_left = any_hops_left() + + originator_address = any_mac_address() + final_destination_address = any_mac_address() + + v = int(originator_address.type == common.MacAddress.SHORT) + f = int(final_destination_address.type == common.MacAddress.SHORT) + + mesh_header_data = bytearray([(2 << 6) | (v << 5) | (f << 4) | hops_left]) + \ + originator_address.mac_address + final_destination_address.mac_address + + mesh_header_factory = lowpan.LowpanMeshHeaderFactory() + + # WHEN + mesh_header = mesh_header_factory.parse(io.BytesIO(mesh_header_data), None) + + # THEN + self.assertEqual(hops_left, mesh_header.hops_left) + self.assertEqual(originator_address, mesh_header.originator_address) + self.assertEqual(final_destination_address, mesh_header.final_destination_address) + + +class TestLowpanFragmentationHeader(unittest.TestCase): + + def test_should_return_datagram_size_value_when_datagram_size_property_is_called(self): + # GIVEN + datagram_size = any_datagram_size() + + fragmentation_header = lowpan.LowpanFragmentationHeader( + datagram_size, any_datagram_tag(), any_datagram_offset()) + + # WHEN + actual_datagram_size = fragmentation_header.datagram_size + + # THEN + self.assertEqual(datagram_size, actual_datagram_size) + + def test_should_return_datagram_tag_value_when_datagram_tag_property_is_called(self): + # GIVEN + datagram_tag = any_datagram_tag() + + fragmentation_header = lowpan.LowpanFragmentationHeader( + any_datagram_size(), datagram_tag, any_datagram_offset()) + + # WHEN + actual_datagram_tag = fragmentation_header.datagram_tag + + # THEN + self.assertEqual(datagram_tag, actual_datagram_tag) + + def test_should_return_datagram_offset_value_when_datagram_offset_property_is_called(self): + # GIVEN + datagram_offset = any_datagram_offset() + + fragmentation_header = lowpan.LowpanFragmentationHeader( + any_datagram_size(), any_datagram_tag(), datagram_offset) + + # WHEN + actual_datagram_offset = fragmentation_header.datagram_offset + + # THEN + self.assertEqual(datagram_offset, actual_datagram_offset) + + def test_should_return_False_when_is_first_property_is_called_and_datagram_offset_is_not_equal_0(self): + # GIVEN + datagram_offset = random.randint(1, (1 << 8) - 1) + + fragmentation_header = lowpan.LowpanFragmentationHeader( + any_datagram_size(), any_datagram_tag(), datagram_offset) + + # WHEN + is_first = fragmentation_header.is_first + + # THEN + self.assertFalse(is_first) + + def test_should_to_bytes_LowpanFragmentationHeader_from_bytes_when_from_bytes_class_method_is_called(self): + # GIVEN + datagram_size = any_datagram_size() + datagram_tag = any_datagram_tag() + datagram_offset = any_datagram_offset() + + data = struct.pack(">HHB", ((3 << 14) | (int(datagram_offset != 0) << 13) | datagram_size), + datagram_tag, datagram_offset) + + # WHEN + fragmentation_header = lowpan.LowpanFragmentationHeader.from_bytes(io.BytesIO(data)) + + # THEN + self.assertEqual(datagram_size, fragmentation_header.datagram_size) + self.assertEqual(datagram_tag, fragmentation_header.datagram_tag) + self.assertEqual(datagram_offset, fragmentation_header.datagram_offset) + + +class TestLowpanDecompressor(unittest.TestCase): + + def test_should_parse_parent_request_when_decompress_method_is_called(self): + # GIVEN + data = bytearray([0x7f, 0x3b, 0x02, 0xf0, 0x4d, 0x4c, 0x4d, 0x4c, + 0x5e, 0xaf, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x3b, 0xfb, 0x0e, + 0x3b, 0x15, 0xa1, 0xf9, 0xf5, 0x64, 0xf4, 0x99, + 0xef, 0x70, 0x78, 0x6c, 0x3c, 0x0f, 0x54, 0x4e, + 0x95, 0xe8, 0xf5, 0x27, 0x4c, 0xfc]) + + message_info = common.MessageInfo() + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x12, 0xcf, 0xd3, 0x8b, 0x3b, 0x61, 0x55, 0x58])) + + decompressor = config.create_default_lowpan_decompressor(context_manager=None) + + # WHEN + ipv6_header, extension_headers, udp_header = decompressor.decompress(io.BytesIO(data), message_info) + + # THEN + self.assertEqual("fe80::10cf:d38b:3b61:5558", ipv6_header.source_address.compressed) + self.assertEqual("ff02::2", ipv6_header.destination_address.compressed) + self.assertEqual(17, ipv6_header.next_header) + self.assertEqual(255, ipv6_header.hop_limit) + + self.assertEqual([], extension_headers) + + def test_should_parse_parent_response_when_decompress_method_is_called(self): + # GIVEN + data = bytearray([0x7f, 0x33, 0xf0, 0x4d, 0x4c, 0x4d, 0x4c, 0x0f, + 0xe8, 0x00, 0x15, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x31, 0xb8, 0x16, 0x02, + 0x61, 0xcc, 0x98, 0x90, 0xd6, 0xfd, 0x69, 0xd3, + 0x89, 0xa0, 0x30, 0x49, 0x83, 0x7c, 0xf7, 0xb5, + 0x7f, 0x83, 0x2a, 0x04, 0xf6, 0x3b, 0x8c, 0xe8, + 0xb6, 0x37, 0x51, 0x5b, 0x28, 0x9a, 0x3b, 0xbe, + 0x0d, 0xb3, 0x4e, 0x9f, 0xd8, 0x14, 0xc8, 0xc9, + 0xf4, 0x28, 0xf6, 0x8d, 0xb7, 0xf0, 0x7d, 0x46, + 0x13, 0xc2, 0xb1, 0x69, 0x4d, 0xae, 0xc1, 0x23, + 0x16, 0x62, 0x90, 0xea, 0xff, 0x1b, 0xb7, 0xd7, + 0x1e, 0x5c]) + + message_info = common.MessageInfo() + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x3a, 0x3e, 0x9e, 0xed, 0x7a, 0x01, 0x36, 0xa5])) + message_info.destination_mac_address = common.MacAddress.from_eui64( + bytearray([0x12, 0xcf, 0xd3, 0x8b, 0x3b, 0x61, 0x55, 0x58])) + + decompressor = config.create_default_lowpan_decompressor(context_manager=None) + + # WHEN + ipv6_header, extension_headers, udp_header = decompressor.decompress(io.BytesIO(data), message_info) + + # THEN + self.assertEqual("fe80::383e:9eed:7a01:36a5", ipv6_header.source_address.compressed) + self.assertEqual("fe80::10cf:d38b:3b61:5558", ipv6_header.destination_address.compressed) + self.assertEqual(17, ipv6_header.next_header) + self.assertEqual(255, ipv6_header.hop_limit) + + self.assertEqual([], extension_headers) + + self.assertEqual(19788, udp_header.src_port) + self.assertEqual(19788, udp_header.dst_port) + + def test_should_parse_child_id_request_when_decompress_method_is_called(self): + # GIVEN + data = bytearray([0x7f, 0x33, 0xf0, 0x4d, 0x4c, 0x4d, 0x4c, 0x9a, + 0x62, 0x00, 0x15, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x14, 0x03, 0xe3, 0x72, + 0x50, 0x4f, 0x8c, 0x5c, 0x42, 0x81, 0x68, 0xe2, + 0x11, 0xfc, 0xf5, 0x8c, 0x62, 0x8e, 0x83, 0x99, + 0xe7, 0x26, 0x86, 0x34, 0x3b, 0xa7, 0x68, 0xc7, + 0x93, 0xfb, 0x72, 0xd9, 0xcc, 0x13, 0x5e, 0x5b, + 0x96, 0x0e, 0xf1, 0x80, 0x03, 0x55, 0x4f, 0x27, + 0xc2, 0x96, 0xf4, 0x9c, 0x65, 0x82, 0x97, 0xcf, + 0x97, 0x35, 0x89, 0xc2]) + + message_info = common.MessageInfo() + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x12, 0xcf, 0xd3, 0x8b, 0x3b, 0x61, 0x55, 0x58])) + message_info.destination_mac_address = common.MacAddress.from_eui64( + bytearray([0x3a, 0x3e, 0x9e, 0xed, 0x7a, 0x01, 0x36, 0xa5])) + + decompressor = config.create_default_lowpan_decompressor(context_manager=None) + + # WHEN + ipv6_header, extension_headers, udp_header = decompressor.decompress(io.BytesIO(data), message_info) + + # THEN + self.assertEqual("fe80::10cf:d38b:3b61:5558", ipv6_header.source_address.compressed) + self.assertEqual("fe80::383e:9eed:7a01:36a5", ipv6_header.destination_address.compressed) + self.assertEqual(17, ipv6_header.next_header) + self.assertEqual(255, ipv6_header.hop_limit) + + self.assertEqual([], extension_headers) + + self.assertEqual(19788, udp_header.src_port) + self.assertEqual(19788, udp_header.dst_port) + + def test_should_parse_child_id_response_when_decompress_method_is_called(self): + # GIVEN + data = bytearray([0x7f, 0x33, 0xf0, 0x4d, 0x4c, 0x4d, 0x4c, 0x7b, + 0xe3, 0x00, 0x15, 0x05, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0xe0, 0x57, 0xbf, 0x2f, + 0xc0, 0x4b, 0x1d, 0xac, 0x3c, 0x24, 0x16, 0xdf, + 0xeb, 0x96, 0xeb, 0xda, 0x42, 0xeb, 0x00, 0x89, + 0x5f, 0x39, 0xc9, 0x2b, 0x7d, 0x31, 0xd5, 0x83, + 0x9d, 0xdb, 0xb7, 0xc8, 0xe6, 0x25, 0xd3, 0x7a, + 0x1e, 0x5f, 0x66, 0x9e, 0x63, 0x2d, 0x42, 0x27, + 0x19, 0x41, 0xdc, 0xc4, 0xc4, 0xc0, 0x8c, 0x07]) + + message_info = common.MessageInfo() + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x3a, 0x3e, 0x9e, 0xed, 0x7a, 0x01, 0x36, 0xa5])) + message_info.destination_mac_address = common.MacAddress.from_eui64( + bytearray([0x12, 0xcf, 0xd3, 0x8b, 0x3b, 0x61, 0x55, 0x58])) + + decompressor = config.create_default_lowpan_decompressor(context_manager=None) + + # WHEN + ipv6_header, extension_headers, udp_header = decompressor.decompress(io.BytesIO(data), message_info) + + # THEN + self.assertEqual("fe80::383e:9eed:7a01:36a5", ipv6_header.source_address.compressed) + self.assertEqual("fe80::10cf:d38b:3b61:5558", ipv6_header.destination_address.compressed) + self.assertEqual(17, ipv6_header.next_header) + self.assertEqual(255, ipv6_header.hop_limit) + + self.assertEqual([], extension_headers) + + self.assertEqual(19788, udp_header.src_port) + self.assertEqual(19788, udp_header.dst_port) + + def test_should_parse_advertisement_when_decompress_method_is_called(self): + # GIVEN + data = bytearray([0x7f, 0x3b, 0x01, 0xf0, 0x4d, 0x4c, 0x4d, 0x4c, + 0x35, 0x9f, 0x00, 0x15, 0x07, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x9e, 0xb8, 0xd0, + 0x2f, 0x2a, 0xe0, 0x00, 0x5d, 0x66, 0x63, 0x05, + 0xa0, 0x59, 0xb0, 0xd4, 0x95, 0x7f, 0xe6, 0x79, + 0x17, 0x87, 0x2c, 0x1d, 0x83, 0xad, 0xc2, 0x64, + 0x47, 0x20, 0x7a, 0xe2]) + + message_info = common.MessageInfo() + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x3a, 0x3e, 0x9e, 0xed, 0x7a, 0x01, 0x36, 0xa5])) + + decompressor = config.create_default_lowpan_decompressor(context_manager=None) + + # WHEN + ipv6_header, extension_headers, udp_header = decompressor.decompress(io.BytesIO(data), message_info) + + # THEN + self.assertEqual("fe80::383e:9eed:7a01:36a5", ipv6_header.source_address.compressed) + self.assertEqual("ff02::1", ipv6_header.destination_address.compressed) + self.assertEqual(17, ipv6_header.next_header) + self.assertEqual(255, ipv6_header.hop_limit) + + self.assertEqual([], extension_headers) + + self.assertEqual(19788, udp_header.src_port) + self.assertEqual(19788, udp_header.dst_port) + + +class TestLowpanFragmentsBuffer(unittest.TestCase): + + def test_should_raise_ValueError_when_write_method_is_called_with_data_length_bigger_than_buffer_length(self): + # GIVEN + length = random.randint(1, 1280) + + fragments_buffer = lowpan.LowpanFragmentsBuffer(buffer_size=(length - 1)) + + # THEN + self.assertRaises(ValueError, fragments_buffer.write, any_data(length)) + + def test_should_move_write_position_by_the_data_length_when_write_method_is_called(self): + # GIVEN + length = random.randint(1, 1280) + + fragments_buffer = lowpan.LowpanFragmentsBuffer(buffer_size=length) + + start_position = fragments_buffer.tell() + + data = any_data(length=random.randint(1, length)) + + # WHEN + fragments_buffer.write(data) + + # THEN + self.assertEqual(fragments_buffer.tell() - start_position, len(data)) + + def test_should_raise_ValueError_when_read_method_is_called_but_not_whole_packet_has_been_stored_in_buffer(self): + # GIVEN + data = any_data(length=3) + + fragments_buffer = lowpan.LowpanFragmentsBuffer(buffer_size=random.randint(4, 1280)) + fragments_buffer.write(data) + + # WHEN + self.assertRaises(ValueError, fragments_buffer.read) + + def test_should_raise_ValueError_when_seek_method_is_called_with_offset_bigger_than_buffer_length(self): + # GIVEN + offset = random.randint(1281, 2500) + + fragments_buffer = lowpan.LowpanFragmentsBuffer(buffer_size=1280) + + # THEN + self.assertRaises(ValueError, fragments_buffer.seek, offset) + + def test_should_set_write_position_when_seek_method_is_called(self): + # GIVEN + length = random.randint(1, 1280) + offset = random.randint(0, length - 1) + + fragments_buffer = lowpan.LowpanFragmentsBuffer(buffer_size=length) + + # WHEN + fragments_buffer.seek(offset) + + # THEN + self.assertEqual(offset, fragments_buffer.tell()) + + def test_should_write_whole_packet_to_buffer_when_write_method_is_called(self): + # GIVEN + data = any_data(length=random.randint(1, 1280)) + + fragments_buffer = lowpan.LowpanFragmentsBuffer(buffer_size=len(data)) + + # WHEN + fragments_buffer.write(data) + + # THEN + self.assertEqual(data, fragments_buffer.read()) + + def test_should_write_many_fragments_to_the_buffer_and_return_whole_message_when_write_method_is_called_many_times(self): + # GIVEN + buffer_size = 42 + fragments_buffer = lowpan.LowpanFragmentsBuffer(buffer_size=buffer_size) + + offset_1 = 0 + fragment_1 = bytearray([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]) + + offset_2 = 8 + fragment_2 = bytearray([0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10]) + + offset_3 = 16 + fragment_3 = bytearray([0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18]) + + offset_4 = 24 + fragment_4 = bytearray([0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20]) + + offset_5 = 32 + fragment_5 = bytearray([0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28]) + + offset_6 = 40 + fragment_6 = bytearray([0x29, 0x2a]) + + # WHEN + fragments_buffer.seek(offset_1) + fragments_buffer.write(fragment_1) + + fragments_buffer.seek(offset_2) + fragments_buffer.write(fragment_2) + + fragments_buffer.seek(offset_3) + fragments_buffer.write(fragment_3) + + fragments_buffer.seek(offset_4) + fragments_buffer.write(fragment_4) + + fragments_buffer.seek(offset_5) + fragments_buffer.write(fragment_5) + + fragments_buffer.seek(offset_6) + fragments_buffer.write(fragment_6) + + # THEN + self.assertEqual(bytearray([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a]), + fragments_buffer.read()) + + +class TestLowpanFragmentsBuffersManager(unittest.TestCase): + + def test_should_raise_ValueError_when_get_fragments_buffer_method_is_called_with_invalid_datagram_size(self): + # GIVEN + message_info = common.MessageInfo() + message_info.source_mac_address = any_mac_address() + message_info.destination_mac_address = any_mac_address() + + negative_int = -random.randint(1, 1280) + + manager = lowpan.LowpanFragmentsBuffersManager() + + # THEN + self.assertRaises(ValueError, manager.get_fragments_buffer, message_info, any_datagram_tag(), None) + self.assertRaises(ValueError, manager.get_fragments_buffer, message_info, any_datagram_tag(), negative_int) + + def test_should_return_LowpanFragmentsBuffer_when_get_fragments_buffer_method_is_called_with_valid_datagram_size(self): + # GIVEN + message_info = common.MessageInfo() + message_info.source_mac_address = any_mac_address() + message_info.destination_mac_address = any_mac_address() + + datagram_size = any_datagram_size() + + manager = lowpan.LowpanFragmentsBuffersManager() + + # WHEN + fragments_buffer = manager.get_fragments_buffer(message_info, any_datagram_tag(), datagram_size) + + # THEN + self.assertIsInstance(fragments_buffer, lowpan.LowpanFragmentsBuffer) + self.assertEqual(datagram_size, len(fragments_buffer)) + +if __name__ == "__main__": + unittest.main(verbosity=1) diff --git a/tests/scripts/thread-cert/test_mac802154.py b/tests/scripts/thread-cert/test_mac802154.py new file mode 100644 index 000000000..44718f05d --- /dev/null +++ b/tests/scripts/thread-cert/test_mac802154.py @@ -0,0 +1,235 @@ +#!/usr/bin/python +# +# Copyright (c) 2016, 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 io +import unittest + +from common import MacAddress +import mac802154 + +longaddrs = bytearray([0x61, 0xcc, 0x00, 0xce, 0xfa]) +shortaddrs = bytearray([0x61, 0x88, 0x00, 0xce, 0xfa]) +longshortaddrs = bytearray([0x61, 0xc8, 0x00, 0xce, 0xfa]) +shortlongaddrs = bytearray([0x61, 0x8c, 0x00, 0xce, 0xfa]) + + +class TestMacParser(unittest.TestCase): + + def test_should_parse_ack_frame(self): + frame = mac802154.MacFrame() + frame.parse(io.BytesIO(bytearray([0x12, 0x00, 0x12, 0x34, 0x56]))) + + self.assertEqual(mac802154.MacHeader.FrameType.ACK, frame.header.frame_type) + self.assertEqual(True, frame.header.frame_pending) + self.assertEqual(False, frame.header.ack_request) + self.assertEqual(0, frame.header.frame_version) + self.assertEqual(0x12, frame.header.seq) + self.assertEqual(bytearray([0x34, 0x56]), frame.header.fcs) + self.assertEqual(None, frame.payload) + + def test_should_parse_data_frame_with_short_addresses(self): + frame = mac802154.MacFrame() + frame.parse(io.BytesIO(bytearray([0x61, 0x88, 0x34, 0xce, 0xfa, + 0xad, 0xde, 0xef, 0xbe, 0x12, 0x34, 0xfe, 0xdc]))) + + self.assertEqual(mac802154.MacHeader.FrameType.DATA, frame.header.frame_type) + self.assertEqual(False, frame.header.frame_pending) + self.assertEqual(True, frame.header.ack_request) + self.assertEqual(0, frame.header.frame_version) + self.assertEqual(0x34, frame.header.seq) + self.assertEqual(bytearray([0xfe, 0xdc]), frame.header.fcs) + self.assertEqual(0xface, frame.header.dest_pan_id) + self.assertEqual(0xdead, frame.header.dest_address.rloc) + self.assertEqual(0xface, frame.header.src_pan_id) + self.assertEqual(0xbeef, frame.header.src_address.rloc) + + self.assertEqual(bytearray([0x12, 0x34]), frame.payload.data) + + def test_should_parse_data_frame_with_extended_addresses(self): + frame = mac802154.MacFrame() + frame.parse(io.BytesIO(bytearray([0x61, 0xcc, + 0x56, + 0xce, 0xfa, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0x0b, 0xad, 0xf0, 0x0d, 0xba, 0xd0, 0xd0, 0x0d, + 0x12, 0x34, + 0xfe, 0xdc]))) + + self.assertEqual(mac802154.MacHeader.FrameType.DATA, frame.header.frame_type) + self.assertEqual(False, frame.header.frame_pending) + self.assertEqual(True, frame.header.ack_request) + self.assertEqual(0, frame.header.frame_version) + self.assertEqual(0x56, frame.header.seq) + self.assertEqual(bytearray([0xfe, 0xdc]), frame.header.fcs) + self.assertEqual(0xface, frame.header.dest_pan_id) + self.assertEqual(bytearray(reversed([0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef])), + frame.header.dest_address.mac_address) + self.assertEqual(0xface, frame.header.src_pan_id) + self.assertEqual(bytearray(reversed([0x0b, 0xad, 0xf0, 0x0d, 0xba, 0xd0, 0xd0, 0x0d])), + frame.header.src_address.mac_address) + + self.assertEqual(bytearray([0x12, 0x34]), frame.payload.data) + + def test_should_parse_data_frame_with_short_and_extended_addresses(self): + frame = mac802154.MacFrame() + frame.parse(io.BytesIO(bytearray([0x61, 0xc8, + 0x56, + 0xce, 0xfa, + 0xad, 0xde, + 0x0b, 0xad, 0xf0, 0x0d, 0xba, 0xd0, 0xd0, 0x0d, + 0x12, 0x34, + 0xfe, 0xdc]))) + + self.assertEqual(mac802154.MacHeader.FrameType.DATA, frame.header.frame_type) + self.assertEqual(False, frame.header.frame_pending) + self.assertEqual(True, frame.header.ack_request) + self.assertEqual(0, frame.header.frame_version) + self.assertEqual(0x56, frame.header.seq) + self.assertEqual(bytearray([0xfe, 0xdc]), frame.header.fcs) + self.assertEqual(0xface, frame.header.dest_pan_id) + self.assertEqual(0xdead, frame.header.dest_address.rloc) + self.assertEqual(0xface, frame.header.src_pan_id) + self.assertEqual(bytearray(reversed([0x0b, 0xad, 0xf0, 0x0d, 0xba, 0xd0, 0xd0, 0x0d])), + frame.header.src_address.mac_address) + + self.assertEqual(bytearray([0x12, 0x34]), frame.payload.data) + + def test_should_parse_data_frame_with_extended_and_short_addresses(self): + frame = mac802154.MacFrame() + frame.parse(io.BytesIO(bytearray([0x61, 0x8c, + 0x56, + 0xce, 0xfa, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0x0d, 0xf0, + 0x12, 0x34, + 0xfe, 0xdc]))) + + self.assertEqual(mac802154.MacHeader.FrameType.DATA, frame.header.frame_type) + self.assertEqual(False, frame.header.frame_pending) + self.assertEqual(True, frame.header.ack_request) + self.assertEqual(0, frame.header.frame_version) + self.assertEqual(0x56, frame.header.seq) + self.assertEqual(bytearray([0xfe, 0xdc]), frame.header.fcs) + self.assertEqual(0xface, frame.header.dest_pan_id) + self.assertEqual(bytearray(reversed([0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef])), + frame.header.dest_address.mac_address) + self.assertEqual(0xface, frame.header.src_pan_id) + self.assertEqual(0xf00d, frame.header.src_address.rloc) + + self.assertEqual(bytearray([0x12, 0x34]), frame.payload.data) + + def test_should_parse_data_request_command(self): + frame = mac802154.MacFrame() + frame.parse(io.BytesIO(bytearray([0x63, 0x88, + 0x78, + 0xce, 0xfa, + 0xad, 0xde, + 0x0d, 0xf0, + 0x04, + 0xfe, 0xdc]))) + + self.assertEqual(mac802154.MacHeader.FrameType.COMMAND, frame.header.frame_type) + self.assertEqual(False, frame.header.frame_pending) + self.assertEqual(True, frame.header.ack_request) + self.assertEqual(0, frame.header.frame_version) + self.assertEqual(0x78, frame.header.seq) + self.assertEqual(bytearray([0xfe, 0xdc]), frame.header.fcs) + self.assertEqual(0xface, frame.header.dest_pan_id) + self.assertEqual(0xdead, frame.header.dest_address.rloc) + self.assertEqual(0xface, frame.header.src_pan_id) + self.assertEqual(0xf00d, frame.header.src_address.rloc) + + self.assertEqual(bytearray([]), frame.payload.data) + + def test_should_decrypt_data_frame(self): + + mac802154.DeviceDescriptors.add(0x2001, MacAddress(bytearray([0x16, 0x6e, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x07]), + MacAddress.LONG)) + + frame = mac802154.MacFrame() + frame.parse(io.BytesIO(bytearray([0x69, 0x98, 0x68, # FC, seq + 0xce, 0xfa, # Pan Id + 0x00, 0x20, # Dst addr + 0x01, 0x20, # Src addr + 0x0d, 0x00, 0x00, 0x00, 0x00, 0x01, # Aux Security Header + 0xb5, 0x5a, 0x0d, 0x8e, 0x18, 0x5c, 0xb1, 0x06, # Payload + 0xc4, 0x6f, 0x7d, 0x6b, 0xb5, 0x4a, 0x87, 0x14, + 0xae, 0xdd, 0x8e, 0xb7, 0x37, 0x62, 0x27, 0x48, + 0xc9, 0x53, 0x0c, 0x44, 0x31, 0x59, 0x8b, 0xa2, + 0x83, 0x59, 0xa1, 0x43, + 0x74, 0xe0, 0x2a, 0xf6, # MIC (valid) + 0x99, 0xfc]))) # FCS (valid) + + self.assertEqual(mac802154.MacHeader.FrameType.DATA, frame.header.frame_type) + self.assertEqual(False, frame.header.frame_pending) + self.assertEqual(True, frame.header.ack_request) + self.assertEqual(1, frame.header.frame_version) + self.assertEqual(0x68, frame.header.seq) + self.assertEqual(bytearray([0x99, 0xfc]), frame.header.fcs) + self.assertEqual(0xface, frame.header.dest_pan_id) + self.assertEqual(0x2000, frame.header.dest_address.rloc) + self.assertEqual(0xface, frame.header.src_pan_id) + self.assertEqual(0x2001, frame.header.src_address.rloc) + + self.assertEqual(0, frame.header.aux_sec_header.frame_counter) + self.assertEqual(5, frame.header.aux_sec_header.security_level) + + self.assertEqual(bytes(bytearray([0x7c, 0x77, 0x80, 0xf0, 0x4d, 0x4d, 0x4d, 0x4d, + 0xe0, 0x04, 0x44, 0x02, 0x44, 0x66, 0x13, 0x5f, + 0x22, 0x80, 0xb1, 0x61, 0x02, 0x61, 0x73, 0x11, + 0x2a, 0xff, 0x01, 0x08, 0x16, 0x6e, 0x0a, 0x00, + 0x00, 0x00, 0x00, 0x07])), frame.payload.data) + + def test_should_decrypt_command_frame(self): + frame = mac802154.MacFrame() + frame.parse(io.BytesIO(bytearray([0x6b, 0xdc, 0xce, 0xce, 0xfa, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x0a, 0x6e, 0x16, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x0a, 0x6e, 0x16, 0x0d, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x04, 0x2d, 0xbc, 0x12, 0xbe, + 0x0a, 0x4f]))) + + self.assertEqual(mac802154.MacHeader.FrameType.COMMAND, frame.header.frame_type) + self.assertEqual(False, frame.header.frame_pending) + self.assertEqual(True, frame.header.ack_request) + self.assertEqual(1, frame.header.frame_version) + self.assertEqual(206, frame.header.seq) + self.assertEqual(bytearray([0x0a, 0x4f]), frame.header.fcs) + self.assertEqual(0xface, frame.header.dest_pan_id) + self.assertEqual(bytearray([0x16, 0x6e, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x02]), + frame.header.dest_address.mac_address) + self.assertEqual(0xface, frame.header.src_pan_id) + self.assertEqual(bytearray([0x16, 0x6e, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x03]), + frame.header.src_address.mac_address) + + self.assertEqual(0, frame.header.aux_sec_header.frame_counter) + self.assertEqual(5, frame.header.aux_sec_header.security_level) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/scripts/thread-cert/test_mle.py b/tests/scripts/thread-cert/test_mle.py new file mode 100644 index 000000000..cd428657f --- /dev/null +++ b/tests/scripts/thread-cert/test_mle.py @@ -0,0 +1,1793 @@ +#!/usr/bin/python +# +# Copyright (c) 2016, 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 io +import random +import struct +import unittest + +import common +import config +import net_crypto +import mle +import network_data + +from ipaddress import ip_address + + +def any_address(): + return random.getrandbits(16) + + +def any_receiver(): + return random.getrandbits(1) + + +def any_secure(): + return random.getrandbits(1) + + +def any_device_type(): + return random.getrandbits(1) + + +def any_network_data(): + return random.getrandbits(1) + +mode_map = { + 0x00: {"receiver": 0, "secure": 0, "device_type": 0, "network_data": 0}, + 0x08: {"receiver": 1, "secure": 0, "device_type": 0, "network_data": 0}, + 0x04: {"receiver": 0, "secure": 1, "device_type": 0, "network_data": 0}, + 0x0C: {"receiver": 1, "secure": 1, "device_type": 0, "network_data": 0}, + 0x02: {"receiver": 0, "secure": 0, "device_type": 1, "network_data": 0}, + 0x0A: {"receiver": 1, "secure": 0, "device_type": 1, "network_data": 0}, + 0x06: {"receiver": 0, "secure": 1, "device_type": 1, "network_data": 0}, + 0x0E: {"receiver": 1, "secure": 1, "device_type": 1, "network_data": 0}, + 0x01: {"receiver": 0, "secure": 0, "device_type": 0, "network_data": 1}, + 0x09: {"receiver": 1, "secure": 0, "device_type": 0, "network_data": 1}, + 0x05: {"receiver": 0, "secure": 1, "device_type": 0, "network_data": 1}, + 0x0D: {"receiver": 1, "secure": 1, "device_type": 0, "network_data": 1}, + 0x03: {"receiver": 0, "secure": 0, "device_type": 1, "network_data": 1}, + 0x0B: {"receiver": 1, "secure": 0, "device_type": 1, "network_data": 1}, + 0x07: {"receiver": 0, "secure": 1, "device_type": 1, "network_data": 1}, + 0x0F: {"receiver": 1, "secure": 1, "device_type": 1, "network_data": 1} +} + + +def any_mode(): + return random.getrandbits(4) + + +def any_timeout(): + return random.getrandbits(32) + + +def any_challenge(): + length = random.randint(4, 8) + return bytearray(random.getrandbits(8) for _ in xrange(length)) + + +def any_response(): + length = random.randint(4, 8) + return bytearray(random.getrandbits(8) for _ in xrange(length)) + + +def any_link_layer_frame_counter(): + return random.getrandbits(32) + + +def any_mle_frame_counter(): + return random.getrandbits(32) + + +def any_output(): + return random.getrandbits(2) + + +def any_input(): + return random.getrandbits(2) + + +def any_route(): + return random.getrandbits(4) + + +def any_id_sequence(): + return random.getrandbits(1) + + +def any_router_id_mask(): + return random.getrandbits(64) + + +def any_link_quality_and_route_data(length=None): + length = length if length is not None else random.randint(0, 63) + return [random.getrandbits(8) for _ in xrange(length)] + + +def any_partition_id(): + return random.getrandbits(32) + + +def any_weighting(): + return random.getrandbits(8) + + +def any_data_version(): + return random.getrandbits(8) + + +def any_stable_data_version(): + return random.getrandbits(8) + + +def any_leader_router_id(): + return random.getrandbits(8) + +scan_mask_map = { + 0x00: {"router": 0, "end_device": 0}, + 0x40: {"router": 0, "end_device": 1}, + 0x80: {"router": 1, "end_device": 0}, + 0xC0: {"router": 1, "end_device": 1}, +} + + +def any_scan_mask_router(): + return random.getrandbits(1) + + +def any_scan_mask_end_device(): + return random.getrandbits(1) + + +def any_scan_mask(): + return (random.getrandbits(2) << 6) + + +def any_link_margin(): + return random.getrandbits(8) + + +def any_status(): + return random.getrandbits(8) + + +def any_version(): + return random.getrandbits(16) + + +def any_channel_page(): + return random.getrandbits(8) + + +def any_channel(): + return random.getrandbits(16) + + +def any_pan_id(): + return random.getrandbits(16) + + +def any_timestamp_seconds(): + return random.getrandbits(48) + + +def any_timestamp_ticks(): + return random.getrandbits(15) + + +def any_u(): + return random.getrandbits(1) + + +def any_pp(): + return random.getrandbits(2) + + +def any_link_quality_3(): + return random.getrandbits(8) + + +def any_link_quality_2(): + return random.getrandbits(8) + + +def any_link_quality_1(): + return random.getrandbits(8) + + +def any_leader_cost(): + return random.getrandbits(8) + + +def any_id_sequence(): + return random.getrandbits(8) + + +def any_active_routers(): + return random.getrandbits(8) + + +def any_sed_buffer_size(): + return random.getrandbits(16) + + +def any_sed_datagram_count(): + return random.getrandbits(8) + + +def any_tlvs(length=None): + if length is None: + length = random.randint(0, 16) + + return [random.getrandbits(8) for _ in xrange(length)] + + +def any_cid(): + return random.getrandbits(4) + + +def any_iid(): + return bytearray([random.getrandbits(8) for _ in xrange(8)]) + + +def any_ipv6_address(): + return bytearray([random.getrandbits(8) for _ in xrange(16)]) + + +def any_addresses(): + addresses = [ + mle.AddressCompressed(any_cid(), any_iid()), + mle.AddressFull(any_ipv6_address()) + ] + + return addresses + + +def any_key_id_mode(): + return random.getrandbits(2) + + +def any_security_level(): + return random.getrandbits(3) + + +def any_frame_counter(): + return random.getrandbits(32) + + +def any_key_id(key_id_mode): + if key_id_mode == 0: + length = 0 + elif key_id_mode == 1: + length = 1 + elif key_id_mode == 2: + length = 5 + elif key_id_mode == 3: + length = 9 + + return bytearray([random.getrandbits(8) for _ in xrange(length)]) + + +def any_eui64(): + return bytearray([random.getrandbits(8) for _ in xrange(8)]) + + +class TestSourceAddress(unittest.TestCase): + + def test_should_return_address_value_when_address_property_is_called(self): + # GIVEN + address = any_address() + + source_address = mle.SourceAddress(address) + + # WHEN + actual_address = source_address.address + + # THEN + self.assertEqual(address, actual_address) + + +class TestSourceAddressFactory(unittest.TestCase): + + def test_should_create_SourceAddress_from_bytearray_when_parse_method_is_called(self): + # GIVEN + address = any_address() + + factory = mle.SourceAddressFactory() + + data = struct.pack(">H", address) + + # WHEN + actual_source_address = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_source_address, mle.SourceAddress)) + self.assertEqual(address, actual_source_address.address) + + +class TestMode(unittest.TestCase): + + def test_should_return_receiver_value_when_receiver_property_is_called(self): + # GIVEN + receiver = any_receiver() + + mode = mle.Mode(receiver, any_secure(), any_device_type(), any_network_data()) + + # WHEN + actual_receiver = mode.receiver + + # THEN + self.assertEqual(receiver, actual_receiver) + + def test_should_return_secure_value_when_secure_property_is_called(self): + # GIVEN + secure = any_secure() + + mode = mle.Mode(any_receiver(), secure, any_device_type(), any_network_data()) + + # WHEN + actual_secure = mode.secure + + # THEN + self.assertEqual(secure, actual_secure) + + def test_should_return_device_type_value_when_device_type_property_is_called(self): + # GIVEN + device_type = any_device_type() + + mode = mle.Mode(any_receiver(), any_secure(), device_type, any_network_data()) + + # WHEN + actual_device_type = mode.device_type + + # THEN + self.assertEqual(device_type, actual_device_type) + + def test_should_return_network_data_value_when_network_data_property_is_called(self): + # GIVEN + network_data = any_network_data() + + mode = mle.Mode(any_receiver(), any_secure(), any_device_type(), network_data) + + # WHEN + actual_network_data = mode.network_data + + # THEN + self.assertEqual(network_data, actual_network_data) + + +class TestModeFactory(unittest.TestCase): + + def test_should_create_Mode_from_bytearray_when_parse_method_is_called(self): + # GIVEN + mode = any_mode() + + factory = mle.ModeFactory() + + data = bytearray([mode]) + + # WHEN + actual_mode = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_mode, mle.Mode)) + self.assertEqual(mode_map[mode]["receiver"], actual_mode.receiver) + self.assertEqual(mode_map[mode]["secure"], actual_mode.secure) + self.assertEqual(mode_map[mode]["device_type"], actual_mode.device_type) + self.assertEqual(mode_map[mode]["network_data"], actual_mode.network_data) + + +class TestTimeout(unittest.TestCase): + + def test_should_return_timeout_value_when_timeout_property_is_called(self): + # GIVEN + timeout = any_timeout() + + timeout_obj = mle.Timeout(timeout) + + # WHEN + actual_timeout = timeout_obj.timeout + + # THEN + self.assertEqual(timeout, actual_timeout) + + +class TestTimeoutFactory(unittest.TestCase): + + def test_should_create_Timeout_from_bytearray_when_parse_method_is_called(self): + # GIVEN + timeout = any_timeout() + + factory = mle.TimeoutFactory() + + data = struct.pack(">I", timeout) + + # WHEN + actual_timeout = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_timeout, mle.Timeout)) + self.assertEqual(timeout, actual_timeout.timeout) + + +class TestChallenge(unittest.TestCase): + + def test_should_return_challenge_value_when_challenge_property_is_called(self): + # GIVEN + challenge = any_challenge() + + challenge_obj = mle.Challenge(challenge) + + # WHEN + actual_challenge = challenge_obj.challenge + + # THEN + self.assertEqual(challenge, actual_challenge) + + +class TestChallengeFactory(unittest.TestCase): + + def test_should_create_Challenge_from_bytearray_when_parse_method_is_called(self): + # GIVEN + challenge = any_challenge() + + factory = mle.ChallengeFactory() + + data = challenge + + # WHEN + actual_challenge = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_challenge, mle.Challenge)) + self.assertEqual(challenge, actual_challenge.challenge) + + +class TestResponse(unittest.TestCase): + + def test_should_return_response_value_when_response_property_is_called(self): + # GIVEN + response = any_response() + + response_obj = mle.Response(response) + + # WHEN + actual_response = response_obj.response + + # THEN + self.assertEqual(response, actual_response) + + +class TestResponseFactory(unittest.TestCase): + + def test_should_create_Challenge_from_bytearray_when_parse_method_is_called(self): + # GIVEN + response = any_response() + + factory = mle.ResponseFactory() + + data = response + + # WHEN + actual_response = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_response, mle.Response)) + self.assertEqual(response, actual_response.response) + + +class TestLinkLayerFrameCounter(unittest.TestCase): + + def test_should_return_frame_counter_value_when_frame_counter_property_is_called(self): + # GIVEN + link_layer_frame_counter = any_link_layer_frame_counter() + + link_layer_frame_counter_obj = mle.LinkLayerFrameCounter(link_layer_frame_counter) + + # WHEN + actual_link_layer_frame_counter = link_layer_frame_counter_obj.frame_counter + + # THEN + self.assertEqual(link_layer_frame_counter, actual_link_layer_frame_counter) + + +class TestLinkLayerFrameCounterFactory(unittest.TestCase): + + def test_should_create_LinkLayerFrameCounter_from_bytearray_when_parse_method_is_called(self): + # GIVEN + link_layer_frame_counter = any_link_layer_frame_counter() + + factory = mle.LinkLayerFrameCounterFactory() + + data = struct.pack(">I", link_layer_frame_counter) + + # WHEN + actual_link_layer_frame_counter = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_link_layer_frame_counter, mle.LinkLayerFrameCounter)) + self.assertEqual(link_layer_frame_counter, actual_link_layer_frame_counter.frame_counter) + + +class TestMleFrameCounter(unittest.TestCase): + + def test_should_return_frame_counter_value_when_frame_counter_property_is_called(self): + # GIVEN + mle_frame_counter = any_mle_frame_counter() + + mle_frame_counter_obj = mle.MleFrameCounter(mle_frame_counter) + + # WHEN + actual_mle_frame_counter = mle_frame_counter_obj.frame_counter + + # THEN + self.assertEqual(mle_frame_counter, actual_mle_frame_counter) + + +class TestMleFrameCounterFactory(unittest.TestCase): + + def test_should_create_MleFrameCounter_from_bytearray_when_parse_method_is_called(self): + # GIVEN + mle_frame_counter = any_mle_frame_counter() + + factory = mle.MleFrameCounterFactory() + + data = struct.pack(">I", mle_frame_counter) + + # WHEN + actual_mle_frame_counter = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_mle_frame_counter, mle.MleFrameCounter)) + self.assertEqual(mle_frame_counter, actual_mle_frame_counter.frame_counter) + + +class TestLinkQualityAndRouteData(unittest.TestCase): + + def test_should_return_output_value_when_output_property_is_called(self): + # GIVEN + output = any_output() + + lqrd = mle.LinkQualityAndRouteData(output, any_input(), any_route()) + + # WHEN + actual_output = lqrd.output + + # THEN + self.assertEqual(output, actual_output) + + def test_should_return_input_value_when_input_property_is_called(self): + # GIVEN + _input = any_input() + + lqrd = mle.LinkQualityAndRouteData(any_output(), _input, any_route()) + + # WHEN + actual_input = lqrd.input + + # THEN + self.assertEqual(_input, actual_input) + + def test_should_return_route_value_when_route_property_is_called(self): + # GIVEN + route = any_route() + + lqrd = mle.LinkQualityAndRouteData(any_output(), any_input(), route) + + # WHEN + actual_route = lqrd.route + + # THEN + self.assertEqual(route, actual_route) + + +class TestLinkQualityAndRouteDataFactory(unittest.TestCase): + + def test_should_create_LinkQualityAndRouteData_from_well_known_byte_when_parse_method_is_called(self): + # GIVEN + factory = mle.LinkQualityAndRouteDataFactory() + + data = bytearray([0x66]) + + # WHEN + actual_lqrd = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertEqual(1, actual_lqrd.output) + self.assertEqual(2, actual_lqrd.input) + self.assertEqual(6, actual_lqrd.route) + + def test_should_create_LinkQualityAndRouteData_from_bytearray_when_parse_method_is_called(self): + # GIVEN + output = any_output() + _input = any_input() + route = any_route() + + lqrd = (output << 6) | (_input << 4) | route + + factory = mle.LinkQualityAndRouteDataFactory() + + data = bytearray([lqrd]) + + # WHEN + actual_lqrd = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_lqrd, mle.LinkQualityAndRouteData)) + self.assertEqual(output, actual_lqrd.output) + self.assertEqual(_input, actual_lqrd.input) + self.assertEqual(route, actual_lqrd.route) + + +class TestRoute64(unittest.TestCase): + + def test_should_return_id_sequence_value_when_id_sequence_property_is_called(self): + # GIVEN + id_sequence = any_id_sequence() + + route64_obj = mle.Route64(id_sequence, any_router_id_mask(), any_link_quality_and_route_data()) + + # WHEN + actual_id_sequence = route64_obj.id_sequence + + # THEN + self.assertEqual(id_sequence, actual_id_sequence) + + def test_should_return_router_id_mask_value_when_router_id_mask_property_is_called(self): + # GIVEN + router_id_mask = any_router_id_mask() + + route64_obj = mle.Route64(any_id_sequence(), router_id_mask, any_link_quality_and_route_data()) + + # WHEN + actual_router_id_mask = route64_obj.router_id_mask + + # THEN + self.assertEqual(router_id_mask, actual_router_id_mask) + + def test_should_return_link_quality_and_route_data_value_when_link_quality_and_route_data_property_is_called(self): + # GIVEN + link_quality_and_route_data = any_link_quality_and_route_data() + + route64_obj = mle.Route64(any_id_sequence(), any_router_id_mask(), link_quality_and_route_data) + + # WHEN + actual_link_quality_and_route_data = route64_obj.link_quality_and_route_data + + # THEN + self.assertEqual(link_quality_and_route_data, actual_link_quality_and_route_data) + + +class TestRoute64Factory(unittest.TestCase): + + def test_should_create_Route64_from_bytearray_when_parse_method_is_called(self): + # GIVEN + class DummyLQRDFactory: + + def parse(self, data, context): + return ord(data.read(1)) + + id_sequence = any_id_sequence() + router_id_mask = any_router_id_mask() + + router_count = 0 + for i in xrange(64): + router_count += (router_id_mask >> i) & 0x01 + + link_quality_and_route_data = any_link_quality_and_route_data(router_count) + + factory = mle.Route64Factory(DummyLQRDFactory()) + + data = bytearray([id_sequence]) + struct.pack(">Q", router_id_mask) + bytearray(link_quality_and_route_data) + + # WHEN + actual_route64 = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_route64, mle.Route64)) + self.assertEqual(id_sequence, actual_route64.id_sequence) + self.assertEqual(router_id_mask, actual_route64.router_id_mask) + self.assertEqual([b for b in link_quality_and_route_data], actual_route64.link_quality_and_route_data) + + +class TestAddress16(unittest.TestCase): + + def test_should_return_address_value_when_address_property_is_called(self): + # GIVEN + address = any_address() + + address16 = mle.Address16(address) + + # WHEN + actual_address = address16.address + + # THEN + self.assertEqual(address, actual_address) + + +class TestAddress16Factory(unittest.TestCase): + + def test_should_create_Address16_from_bytearray_when_parse_method_is_called(self): + # GIVEN + address = any_address() + + factory = mle.Address16Factory() + + data = struct.pack(">H", address) + + # WHEN + actual_address16 = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_address16, mle.Address16)) + self.assertEqual(address, actual_address16.address) + + +class TestLeaderData(unittest.TestCase): + + def test_should_return_partition_id_value_when_partition_id_property_is_called(self): + # GIVEN + partition_id = any_partition_id() + + leader_data = mle.LeaderData(partition_id, any_weighting(), any_data_version(), + any_stable_data_version(), any_leader_router_id()) + + # WHEN + actual_partition_id = leader_data.partition_id + + # THEN + self.assertEqual(partition_id, actual_partition_id) + + def test_should_return_weighting_value_when_weighting_property_is_called(self): + # GIVEN + weighting = any_weighting() + + leader_data = mle.LeaderData(any_partition_id(), weighting, any_data_version(), + any_stable_data_version(), any_leader_router_id()) + + # WHEN + actual_weighting = leader_data.weighting + + # THEN + self.assertEqual(weighting, actual_weighting) + + def test_should_return_data_version_value_when_data_version_property_is_called(self): + # GIVEN + data_version = any_data_version() + + leader_data = mle.LeaderData(any_partition_id(), any_weighting(), data_version, + any_stable_data_version(), any_leader_router_id()) + + # WHEN + actual_data_version = leader_data.data_version + + # THEN + self.assertEqual(data_version, actual_data_version) + + def test_should_return_stable_data_version_value_when_stable_data_version_property_is_called(self): + # GIVEN + stable_data_version = any_stable_data_version() + + leader_data = mle.LeaderData(any_partition_id(), any_weighting(), any_data_version(), + stable_data_version, any_leader_router_id()) + + # WHEN + actual_stable_data_version = leader_data.stable_data_version + + # THEN + self.assertEqual(stable_data_version, actual_stable_data_version) + + def test_should_return_leader_router_id_value_when_leader_router_id_property_is_called(self): + # GIVEN + leader_router_id = any_leader_router_id() + + leader_data = mle.LeaderData(any_partition_id(), any_weighting(), any_data_version(), + any_stable_data_version(), leader_router_id) + + # WHEN + actual_leader_router_id = leader_data.leader_router_id + + # THEN + self.assertEqual(leader_router_id, actual_leader_router_id) + + +class TestLeaderDataFactory(unittest.TestCase): + + def test_should_create_Address16_from_bytearray_when_parse_method_is_called(self): + # GIVEN + partition_id = any_partition_id() + weighting = any_weighting() + data_version = any_data_version() + stable_data_version = any_stable_data_version() + leader_router_id = any_leader_router_id() + + factory = mle.LeaderDataFactory() + + data = bytearray(struct.pack(">I", partition_id)) + \ + bytearray([weighting, data_version, stable_data_version, leader_router_id]) + + # WHEN + actual_leader_data = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_leader_data, mle.LeaderData)) + self.assertEqual(partition_id, actual_leader_data.partition_id) + self.assertEqual(weighting, actual_leader_data.weighting) + self.assertEqual(data_version, actual_leader_data.data_version) + self.assertEqual(stable_data_version, actual_leader_data.stable_data_version) + self.assertEqual(leader_router_id, actual_leader_data.leader_router_id) + + +class TestNetworkData(unittest.TestCase): + + def test_should_return_tlvs_value_when_tlvs_property_is_called(self): + # GIVEN + tlvs = any_tlvs() + + network_data = mle.NetworkData(tlvs) + + # WHEN + actual_tlvs = network_data.tlvs + + # THEN + self.assertEqual(tlvs, actual_tlvs) + + +class TestNetworkDataFactory(unittest.TestCase): + + def test_should_create_TlvRequest_from_bytearray_when_parse_method_is_called(self): + # GIVEN + class DummyNetworkTlvsFactory: + + def parse(self, data, context): + return [ord(b) for b in data.read()] + + tlvs = any_tlvs() + + factory = mle.NetworkDataFactory(DummyNetworkTlvsFactory()) + + data = bytearray(tlvs) + + # WHEN + actual_network_data = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_network_data, mle.NetworkData)) + self.assertEqual(tlvs, actual_network_data.tlvs) + + +class TestTlvRequest(unittest.TestCase): + + def test_should_return_tlvs_value_when_tlvs_property_is_called(self): + # GIVEN + tlvs = any_tlvs() + + tlv_request = mle.TlvRequest(tlvs) + + # WHEN + actual_tlvs = tlv_request.tlvs + + # THEN + self.assertEqual(tlvs, actual_tlvs) + + +class TestTlvRequestFactory(unittest.TestCase): + + def test_should_create_TlvRequest_from_bytearray_when_parse_method_is_called(self): + # GIVEN + tlvs = any_tlvs() + + factory = mle.TlvRequestFactory() + + data = bytearray(tlvs) + + # WHEN + actual_tlv_request = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_tlv_request, mle.TlvRequest)) + self.assertEqual(tlvs, actual_tlv_request.tlvs) + + +class TestScanMask(unittest.TestCase): + + def test_should_return_router_value_when_router_property_is_called(self): + # GIVEN + router = any_scan_mask_router() + + scan_mask = mle.ScanMask(router, any_scan_mask_end_device()) + + # WHEN + actual_router = scan_mask.router + + # THEN + self.assertEqual(router, actual_router) + + def test_should_return_end_device_value_when_end_device_property_is_called(self): + # GIVEN + end_device = any_scan_mask_end_device() + + scan_mask = mle.ScanMask(any_scan_mask_router(), end_device) + + # WHEN + actual_end_device = scan_mask.end_device + + # THEN + self.assertEqual(end_device, actual_end_device) + + +class TestScanMaskFactory(unittest.TestCase): + + def test_should_create_ScanMask_from_bytearray_when_parse_method_is_called(self): + # GIVEN + scan_mask = any_scan_mask() + + factory = mle.ScanMaskFactory() + + data = bytearray([scan_mask]) + + # WHEN + actual_scan_mask = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_scan_mask, mle.ScanMask)) + self.assertEqual(scan_mask_map[scan_mask]["router"], actual_scan_mask.router) + self.assertEqual(scan_mask_map[scan_mask]["end_device"], actual_scan_mask.end_device) + + +class TestConnectivity(unittest.TestCase): + + def test_should_return_pp_value_when_pp_property_is_called(self): + # GIVEN + pp = any_pp() + + connectivity_obj = mle.Connectivity(pp, + any_link_quality_3(), + any_link_quality_2(), + any_link_quality_1(), + any_leader_cost(), + any_id_sequence(), + any_active_routers(), + any_sed_buffer_size(), + any_sed_datagram_count()) + + # WHEN + actual_pp = connectivity_obj.pp + + # THEN + self.assertEqual(pp, actual_pp) + + def test_should_return_link_quality_3_value_when_link_quality_3_property_is_called(self): + # GIVEN + link_quality_3 = any_link_quality_3() + + connectivity_obj = mle.Connectivity(any_pp(), + link_quality_3, + any_link_quality_2(), + any_link_quality_1(), + any_leader_cost(), + any_id_sequence(), + any_active_routers(), + any_sed_buffer_size(), + any_sed_datagram_count()) + + # WHEN + actual_link_quality_3 = connectivity_obj.link_quality_3 + + # THEN + self.assertEqual(link_quality_3, actual_link_quality_3) + + def test_should_return_link_quality_2_value_when_link_quality_2_property_is_called(self): + # GIVEN + link_quality_2 = any_link_quality_2() + + connectivity_obj = mle.Connectivity(any_pp(), + any_link_quality_3(), + link_quality_2, + any_link_quality_1(), + any_leader_cost(), + any_id_sequence(), + any_active_routers(), + any_sed_buffer_size(), + any_sed_datagram_count()) + + # WHEN + actual_link_quality_2 = connectivity_obj.link_quality_2 + + # THEN + self.assertEqual(link_quality_2, actual_link_quality_2) + + def test_should_return_link_quality_1_value_when_link_quality_1_property_is_called(self): + # GIVEN + link_quality_1 = any_link_quality_1() + + connectivity_obj = mle.Connectivity(any_pp(), + any_link_quality_3(), + any_link_quality_2(), + link_quality_1, + any_leader_cost(), + any_id_sequence(), + any_active_routers(), + any_sed_buffer_size(), + any_sed_datagram_count()) + + # WHEN + actual_link_quality_1 = connectivity_obj.link_quality_1 + + # THEN + self.assertEqual(link_quality_1, actual_link_quality_1) + + def test_should_return_leader_cost_value_when_leader_cost_property_is_called(self): + # GIVEN + leader_cost = any_leader_cost() + + connectivity_obj = mle.Connectivity(any_pp(), + any_link_quality_3(), + any_link_quality_2(), + any_link_quality_1(), + leader_cost, + any_id_sequence(), + any_active_routers(), + any_sed_buffer_size(), + any_sed_datagram_count()) + + # WHEN + actual_leader_cost = connectivity_obj.leader_cost + + # THEN + self.assertEqual(leader_cost, actual_leader_cost) + + def test_should_return_id_sequence_value_when_id_sequence_property_is_called(self): + # GIVEN + id_sequence = any_id_sequence() + + connectivity_obj = mle.Connectivity(any_pp(), + any_link_quality_3(), + any_link_quality_2(), + any_link_quality_1(), + any_leader_cost(), + id_sequence, + any_active_routers(), + any_sed_buffer_size(), + any_sed_datagram_count()) + + # WHEN + actual_id_sequence = connectivity_obj.id_sequence + + # THEN + self.assertEqual(id_sequence, actual_id_sequence) + + def test_should_return_active_routers_value_when_active_routers_property_is_called(self): + # GIVEN + active_routers = any_active_routers() + + connectivity_obj = mle.Connectivity(any_pp(), + any_link_quality_3(), + any_link_quality_2(), + any_link_quality_1(), + any_leader_cost(), + any_id_sequence(), + active_routers, + any_sed_buffer_size(), + any_sed_datagram_count()) + + # WHEN + actual_active_routers = connectivity_obj.active_routers + + # THEN + self.assertEqual(active_routers, actual_active_routers) + + def test_should_return_sed_buffer_size_value_when_sed_buffer_size_property_is_called(self): + # GIVEN + sed_buffer_size = any_sed_buffer_size() + + connectivity_obj = mle.Connectivity(any_pp(), + any_link_quality_3(), + any_link_quality_2(), + any_link_quality_1(), + any_leader_cost(), + any_id_sequence(), + any_active_routers(), + sed_buffer_size, + any_sed_datagram_count()) + + # WHEN + actual_sed_buffer_size = connectivity_obj.sed_buffer_size + + # THEN + self.assertEqual(sed_buffer_size, actual_sed_buffer_size) + + def test_should_return_sed_datagram_count_value_when_sed_datagram_count_property_is_called(self): + # GIVEN + sed_datagram_count = any_sed_datagram_count() + + connectivity_obj = mle.Connectivity(any_pp(), + any_link_quality_3(), + any_link_quality_2(), + any_link_quality_1(), + any_leader_cost(), + any_id_sequence(), + any_active_routers(), + any_sed_buffer_size(), + sed_datagram_count) + + # WHEN + actual_sed_datagram_count = connectivity_obj.sed_datagram_count + + # THEN + self.assertEqual(sed_datagram_count, actual_sed_datagram_count) + + +class TestConnectivityFactory(unittest.TestCase): + + def test_should_create_Connectivity_from_bytearray_when_parse_method_is_called(self): + # GIVEN + pp = any_pp() + link_quality_3 = any_link_quality_3() + link_quality_2 = any_link_quality_2() + link_quality_1 = any_link_quality_1() + leader_cost = any_leader_cost() + id_sequence = any_id_sequence() + active_routers = any_active_routers() + sed_buffer_size = any_sed_buffer_size() + sed_datagram_count = any_sed_datagram_count() + + factory = mle.ConnectivityFactory() + + data = bytearray([pp, link_quality_3, link_quality_2, link_quality_1, leader_cost, id_sequence, + active_routers]) + struct.pack(">H", sed_buffer_size) + bytearray([sed_datagram_count]) + + # WHEN + actual_connectivity = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_connectivity, mle.Connectivity)) + self.assertEqual(pp, actual_connectivity.pp) + self.assertEqual(link_quality_3, actual_connectivity.link_quality_3) + self.assertEqual(link_quality_2, actual_connectivity.link_quality_2) + self.assertEqual(link_quality_1, actual_connectivity.link_quality_1) + self.assertEqual(leader_cost, actual_connectivity.leader_cost) + self.assertEqual(id_sequence, actual_connectivity.id_sequence) + self.assertEqual(active_routers, actual_connectivity.active_routers) + self.assertEqual(sed_buffer_size, actual_connectivity.sed_buffer_size) + self.assertEqual(sed_datagram_count, actual_connectivity.sed_datagram_count) + + def test_should_create_Connectivity_without_sed_data_when_parse_method_is_called(self): + # GIVEN + pp = any_pp() + link_quality_3 = any_link_quality_3() + link_quality_2 = any_link_quality_2() + link_quality_1 = any_link_quality_1() + leader_cost = any_leader_cost() + id_sequence = any_id_sequence() + active_routers = any_active_routers() + sed_buffer_size = any_sed_buffer_size() + sed_datagram_count = any_sed_datagram_count() + + factory = mle.ConnectivityFactory() + + data = bytearray([pp, link_quality_3, link_quality_2, link_quality_1, leader_cost, id_sequence, + active_routers]) + + # WHEN + actual_connectivity = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_connectivity, mle.Connectivity)) + self.assertEqual(pp, actual_connectivity.pp) + self.assertEqual(link_quality_3, actual_connectivity.link_quality_3) + self.assertEqual(link_quality_2, actual_connectivity.link_quality_2) + self.assertEqual(link_quality_1, actual_connectivity.link_quality_1) + self.assertEqual(leader_cost, actual_connectivity.leader_cost) + self.assertEqual(id_sequence, actual_connectivity.id_sequence) + self.assertEqual(active_routers, actual_connectivity.active_routers) + self.assertEqual(None, actual_connectivity.sed_buffer_size) + self.assertEqual(None, actual_connectivity.sed_datagram_count) + + +class TestLinkMargin(unittest.TestCase): + + def test_should_return_link_margin_value_when_link_margin_property_is_called(self): + # GIVEN + link_margin = any_link_margin() + + link_margin_obj = mle.LinkMargin(link_margin) + + # WHEN + actual_link_margin = link_margin_obj.link_margin + + # THEN + self.assertEqual(link_margin, actual_link_margin) + + +class TestLinkMarginFactory(unittest.TestCase): + + def test_should_create_LinkMargin_from_bytearray_when_parse_method_is_called(self): + # GIVEN + link_margin = any_link_margin() + + factory = mle.LinkMarginFactory() + + data = bytearray([link_margin]) + + # WHEN + actual_link_margin = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_link_margin, mle.LinkMargin)) + self.assertEqual(link_margin, actual_link_margin.link_margin) + + +class TestStatus(unittest.TestCase): + + def test_should_return_status_value_when_status_property_is_called(self): + # GIVEN + status = any_status() + + status_obj = mle.Status(status) + + # WHEN + actual_status = status_obj.status + + # THEN + self.assertEqual(status, actual_status) + + +class TestStatusFactory(unittest.TestCase): + + def test_should_create_Status_from_bytearray_when_parse_method_is_called(self): + # GIVEN + status = any_status() + + factory = mle.StatusFactory() + + data = bytearray([status]) + + # WHEN + actual_status = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_status, mle.Status)) + self.assertEqual(status, actual_status.status) + + +class TestVersion(unittest.TestCase): + + def test_should_return_version_value_when_version_property_is_called(self): + # GIVEN + version = any_version() + + version_obj = mle.Version(version) + + # WHEN + actual_version = version_obj.version + + # THEN + self.assertEqual(version, actual_version) + + +class TestVersionFactory(unittest.TestCase): + + def test_should_create_Version_from_bytearray_when_parse_method_is_called(self): + # GIVEN + version = any_version() + + factory = mle.VersionFactory() + + data = struct.pack(">H", version) + + # WHEN + actual_version = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_version, mle.Version)) + self.assertEqual(version, actual_version.version) + + +class TestAddressRegistrationFull(unittest.TestCase): + + def test_should_return_ipv6_address_value_when_ipv6_address_property_is_called(self): + # GIVEN + ipv6_address = any_ipv6_address() + + addr_reg_full_obj = mle.AddressFull(ipv6_address) + + # WHEN + actual_ipv6_address = addr_reg_full_obj.ipv6_address + + # THEN + self.assertEqual(ipv6_address, actual_ipv6_address) + + +class TestAddressRegistrationFullFactory(unittest.TestCase): + + def test_should_create_AddressFull_from_bytearray_when_parse_method_is_called(self): + # GIVEN + ipv6_address = any_ipv6_address() + + factory = mle.AddressFullFactory() + + data = bytearray([0x00]) + bytearray(ipv6_address) + + # WHEN + actual_addr_reg_full = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_addr_reg_full, mle.AddressFull)) + self.assertEqual(ipv6_address, actual_addr_reg_full.ipv6_address) + + +class TestAddressRegistrationCompressed(unittest.TestCase): + + def test_should_return_cid_value_when_cid_property_is_called(self): + # GIVEN + cid = any_cid() + + addr_reg_compressed_obj = mle.AddressCompressed(cid, any_iid()) + + # WHEN + actual_cid = addr_reg_compressed_obj.cid + + # THEN + self.assertEqual(cid, actual_cid) + + def test_should_return_cid_value_when_iid_property_is_called(self): + # GIVEN + iid = any_iid() + + addr_reg_compressed_obj = mle.AddressCompressed(any_cid(), iid) + + # WHEN + actual_iid = addr_reg_compressed_obj.iid + + # THEN + self.assertEqual(iid, actual_iid) + + +class TestAddressRegistrationCompressedFactory(unittest.TestCase): + + def test_should_create_AddressRegistrationCompressed_from_bytearray_when_parse_method_is_called(self): + # GIVEN + cid = any_cid() + iid = any_iid() + + factory = mle.AddressCompressedFactory() + + data = bytearray([(1 << 7) | cid]) + iid + + # WHEN + actual_addr_reg_compressed = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_addr_reg_compressed, mle.AddressCompressed)) + self.assertEqual(cid, actual_addr_reg_compressed.cid) + self.assertEqual(iid, actual_addr_reg_compressed.iid) + + +class TestAddressRegistration(unittest.TestCase): + + def test_should_return_addresses_value_when_addresses_property_is_called(self): + # GIVEN + addresses = any_addresses() + + addr_reg_obj = mle.AddressRegistration(addresses) + + # WHEN + actual_addresses = addr_reg_obj.addresses + + # THEN + self.assertEqual(addresses, actual_addresses) + + +class TestAddressRegistrationFactory(unittest.TestCase): + + def test_should_create_AddressRegistration_from_bytearray_when_parse_method_is_called(self): + # GIVEN + cid = any_cid() + iid = any_iid() + ipv6_address = any_ipv6_address() + + addresses = [ + mle.AddressCompressed(cid, iid), + mle.AddressFull(ipv6_address) + ] + + factory = mle.AddressRegistrationFactory(mle.AddressCompressedFactory(), + mle.AddressFullFactory()) + + data = bytearray([(1 << 7) | cid]) + iid + bytearray([0]) + ipv6_address + + # WHEN + actual_addr_reg = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_addr_reg, mle.AddressRegistration)) + self.assertEqual(addresses[0].cid, actual_addr_reg.addresses[0].cid) + self.assertEqual(addresses[0].iid, actual_addr_reg.addresses[0].iid) + self.assertEqual(addresses[1].ipv6_address, actual_addr_reg.addresses[1].ipv6_address) + + +class TestChannel(unittest.TestCase): + + def test_should_return_channel_page_value_when_channel_page_property_is_called(self): + # GIVEN + channel_page = any_channel_page() + + channel_obj = mle.Channel(channel_page, any_channel()) + + # WHEN + actual_channel_page = channel_obj.channel_page + + # THEN + self.assertEqual(channel_page, actual_channel_page) + + def test_should_return_channel_value_when_channel_property_is_called(self): + # GIVEN + channel = any_channel() + + channel_obj = mle.Channel(any_channel_page(), channel) + + # WHEN + actual_channel = channel_obj.channel + + # THEN + self.assertEqual(channel, actual_channel) + + +class TestChannelFactory(unittest.TestCase): + + def test_should_create_Channel_from_bytearray_when_parse_method_is_called(self): + # GIVEN + channel_page = any_channel_page() + channel = any_channel() + + factory = mle.ChannelFactory() + + data = bytearray([channel_page]) + struct.pack(">H", channel) + + # WHEN + actual_channel = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_channel, mle.Channel)) + self.assertEqual(channel_page, actual_channel.channel_page) + self.assertEqual(channel, actual_channel.channel) + + +class TestPanId(unittest.TestCase): + + def test_should_return_pan_id_value_when_pan_id_property_is_called(self): + # GIVEN + pan_id = any_pan_id() + + pan_id_obj = mle.PanId(pan_id) + + # WHEN + actual_pan_id = pan_id_obj.pan_id + + # THEN + self.assertEqual(pan_id, actual_pan_id) + + +class TestPanIdFactory(unittest.TestCase): + + def test_should_create_PanId_from_bytearray_when_parse_method_is_called(self): + # GIVEN + pan_id = any_pan_id() + + factory = mle.PanIdFactory() + + data = struct.pack(">H", pan_id) + + # WHEN + actual_pan_id = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(actual_pan_id, mle.PanId)) + self.assertEqual(pan_id, actual_pan_id.pan_id) + + +class TestActiveTimestamp(unittest.TestCase): + + def test_should_return_timestamp_seconds_value_when_timestamp_seconds_property_is_called(self): + # GIVEN + timestamp_seconds = any_timestamp_seconds() + + active_timestamp_obj = mle.ActiveTimestamp(timestamp_seconds, any_timestamp_ticks(), any_u()) + + # WHEN + actual_timestamp_seconds = active_timestamp_obj.timestamp_seconds + + # THEN + self.assertEqual(timestamp_seconds, actual_timestamp_seconds) + + def test_should_return_timestamp_ticks_value_when_timestamp_ticks_property_is_called(self): + # GIVEN + timestamp_ticks = any_timestamp_ticks() + + active_timestamp_obj = mle.ActiveTimestamp(any_timestamp_seconds(), timestamp_ticks, any_u()) + + # WHEN + actual_timestamp_ticks = active_timestamp_obj.timestamp_ticks + + # THEN + self.assertEqual(timestamp_ticks, actual_timestamp_ticks) + + def test_should_return_u_value_when_u_property_is_called(self): + # GIVEN + u = any_u() + + active_timestamp_obj = mle.ActiveTimestamp(any_timestamp_seconds(), any_timestamp_ticks(), u) + + # WHEN + actual_u = active_timestamp_obj.u + + # THEN + self.assertEqual(u, actual_u) + + +class TestActiveTimestampFactory(unittest.TestCase): + + def test_should_create_ActiveTimestamp_from_bytearray_when_parse_method_is_called(self): + # GIVEN + timestamp_seconds = any_timestamp_seconds() + timestamp_ticks = any_timestamp_ticks() + u = any_u() + + factory = mle.ActiveTimestampFactory() + + data = struct.pack(">Q", timestamp_seconds)[2:] + struct.pack(">H", (timestamp_ticks << 1) | u) + + # WHEN + active_timestamp = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(active_timestamp, mle.ActiveTimestamp)) + self.assertEqual(timestamp_seconds, active_timestamp.timestamp_seconds) + self.assertEqual(timestamp_ticks, active_timestamp.timestamp_ticks) + self.assertEqual(u, active_timestamp.u) + + +class TestPendingTimestamp(unittest.TestCase): + + def test_should_return_timestamp_seconds_value_when_timestamp_seconds_property_is_called(self): + # GIVEN + timestamp_seconds = any_timestamp_seconds() + + pending_timestamp_obj = mle.PendingTimestamp(timestamp_seconds, any_timestamp_ticks(), any_u()) + + # WHEN + actual_timestamp_seconds = pending_timestamp_obj.timestamp_seconds + + # THEN + self.assertEqual(timestamp_seconds, actual_timestamp_seconds) + + def test_should_return_timestamp_ticks_value_when_timestamp_ticks_property_is_called(self): + # GIVEN + timestamp_ticks = any_timestamp_ticks() + + pending_timestamp_obj = mle.PendingTimestamp(any_timestamp_seconds(), timestamp_ticks, any_u()) + + # WHEN + actual_timestamp_ticks = pending_timestamp_obj.timestamp_ticks + + # THEN + self.assertEqual(timestamp_ticks, actual_timestamp_ticks) + + def test_should_return_u_value_when_u_property_is_called(self): + # GIVEN + u = any_u() + + pending_timestamp_obj = mle.PendingTimestamp(any_timestamp_seconds(), any_timestamp_ticks(), u) + + # WHEN + actual_u = pending_timestamp_obj.u + + # THEN + self.assertEqual(u, actual_u) + + +class TestPendingTimestampFactory(unittest.TestCase): + + def test_should_create_PendingTimestamp_from_bytearray_when_parse_method_is_called(self): + # GIVEN + timestamp_seconds = any_timestamp_seconds() + timestamp_ticks = any_timestamp_ticks() + u = any_u() + + factory = mle.PendingTimestampFactory() + + data = struct.pack(">Q", timestamp_seconds)[2:] + struct.pack(">H", (timestamp_ticks << 1) | u) + + # WHEN + pending_timestamp = factory.parse(io.BytesIO(data), dict()) + + # THEN + self.assertTrue(isinstance(pending_timestamp, mle.PendingTimestamp)) + self.assertEqual(timestamp_seconds, pending_timestamp.timestamp_seconds) + self.assertEqual(timestamp_ticks, pending_timestamp.timestamp_ticks) + self.assertEqual(u, pending_timestamp.u) + + +class TestMleCommandFactory(unittest.TestCase): + + def test_should_create_MleCommand_from_bytearray_when_parse_method_is_called(self): + data = bytearray([0x0b, 0x04, 0x08, 0xa5, 0xf2, 0x9b, 0xde, 0xe3, + 0xd8, 0xbe, 0xb9, 0x05, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x04, 0x00, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x0d, 0x02, 0x04, 0x00, 0x00, 0x00, 0xf0, + 0x12, 0x02, 0x00, 0x02, 0x13, 0x09, 0x80, 0x86, + 0xa2, 0x1b, 0x81, 0x6d, 0xb8, 0xb5, 0xe8, 0x0d, + 0x03, 0x0a, 0x0c, 0x09]) + + factory = mle.MleCommandFactory(config.create_default_mle_tlvs_factories()) + + # WHEN + actual_mle_command = factory.parse(io.BytesIO(data), None) + + # THEN + self.assertTrue(isinstance(actual_mle_command, mle.MleCommand)) + + self.assertEqual(11, actual_mle_command.type) + + self.assertEqual(mle.Response(bytearray([0xa5, 0xf2, 0x9b, 0xde, 0xe3, 0xd8, 0xbe, 0xb9])), + actual_mle_command.tlvs[0]) + + self.assertEqual(mle.LinkLayerFrameCounter(0), actual_mle_command.tlvs[1]) + + self.assertEqual(mle.MleFrameCounter(1), actual_mle_command.tlvs[2]) + + self.assertEqual(mle.Mode(receiver=1, secure=1, device_type=0, network_data=1), + actual_mle_command.tlvs[3]) + + self.assertEqual(mle.Timeout(240), actual_mle_command.tlvs[4]) + + self.assertEqual(mle.Version(2), actual_mle_command.tlvs[5]) + + self.assertEqual(mle.AddressRegistration(addresses=[ + mle.AddressCompressed(cid=0, iid=bytearray([0x86, 0xa2, 0x1b, 0x81, 0x6d, 0xb8, 0xb5, 0xe8]))]), + actual_mle_command.tlvs[6]) + + self.assertEqual(mle.TlvRequest(tlvs=[10, 12, 9]), actual_mle_command.tlvs[7]) + + +class TestMleMessageFactory(unittest.TestCase): + + def test_should_create_MleMessageSecured_from_bytearray_when_parse_method_is_called(self): + # GIVEN + message_info = common.MessageInfo() + message_info.source_ipv6 = "fe80::10cf:d38b:3b61:5558" + message_info.destination_ipv6 = "fe80::383e:9eed:7a01:36a5" + + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x12, 0xcf, 0xd3, 0x8b, 0x3b, 0x61, 0x55, 0x58])) + message_info.destination_mac_address = common.MacAddress.from_eui64( + bytearray([0x3a, 0x3e, 0x9e, 0xed, 0x7a, 0x01, 0x36, 0xa5])) + + data = bytearray([0x00, 0x15, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x14, 0x03, 0xe3, 0x72, 0x50, 0x4f, + 0x8c, 0x5c, 0x42, 0x81, 0x68, 0xe2, 0x11, 0xfc, + 0xf5, 0x8c, 0x62, 0x8e, 0x83, 0x99, 0xe7, 0x26, + 0x86, 0x34, 0x3b, 0xa7, 0x68, 0xc7, 0x93, 0xfb, + 0x72, 0xd9, 0xcc, 0x13, 0x5e, 0x5b, 0x96, 0x0e, + 0xf1, 0x80, 0x03, 0x55, 0x4f, 0x27, 0xc2, 0x96, + 0xf4, 0x9c, 0x65, 0x82, 0x97, 0xcf, 0x97, 0x35, + 0x89, 0xc2]) + + factory = config.create_default_mle_message_factory(master_key=config.DEFAULT_MASTER_KEY) + + # WHEN + actual_mle_message = factory.parse(io.BytesIO(data), message_info) + + # THEN + self.assertTrue(isinstance(actual_mle_message, mle.MleMessageSecured)) + + self.assertEqual(11, actual_mle_message.command.type) + + self.assertEqual(mle.Response(bytearray([0xa5, 0xf2, 0x9b, 0xde, 0xe3, 0xd8, 0xbe, 0xb9])), + actual_mle_message.command.tlvs[0]) + + self.assertEqual(mle.LinkLayerFrameCounter(0), actual_mle_message.command.tlvs[1]) + + self.assertEqual(mle.MleFrameCounter(1), actual_mle_message.command.tlvs[2]) + + self.assertEqual(mle.Mode(receiver=1, secure=1, device_type=0, network_data=1), + actual_mle_message.command.tlvs[3]) + + self.assertEqual(mle.Timeout(240), actual_mle_message.command.tlvs[4]) + + self.assertEqual(mle.Version(2), actual_mle_message.command.tlvs[5]) + + self.assertEqual(mle.AddressRegistration(addresses=[ + mle.AddressCompressed(cid=0, iid=bytearray([0x86, 0xa2, 0x1b, 0x81, 0x6d, 0xb8, 0xb5, 0xe8]))]), + actual_mle_message.command.tlvs[6]) + + self.assertEqual(mle.TlvRequest(tlvs=[10, 12, 9]), actual_mle_message.command.tlvs[7]) + + self.assertEqual(bytearray(data[-4:]), actual_mle_message.mic) + + def test_should_create_MleMessageSecured_with_MLE_Data_Response_from_bytearray_when_parse_method_is_called(self): + # GIVEN + message_info = common.MessageInfo() + message_info.source_ipv6 = "fe80::241c:b11b:7b62:caf1" + message_info.destination_ipv6 = "ff02::1" + + message_info.source_mac_address = common.MacAddress.from_eui64( + bytearray([0x26, 0x1c, 0xb1, 0x1b, 0x7b, 0x62, 0xca, 0xf1])) + message_info.destination_mac_address = common.MacAddress.from_eui64( + bytearray([0x3a, 0xba, 0xad, 0xca, 0xfe, 0xde, 0xff, 0xa5])) + + data = bytearray([0x00, 0x15, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0xca, 0xd3, 0x45, 0xe2, 0x35, + 0x1d, 0x00, 0x2d, 0x72, 0x71, 0xb1, 0x19, 0xaf, + 0x8b, 0x05, 0xd9, 0x52, 0x74, 0xce, 0xe6, 0x36, + 0x53, 0xeb, 0xc6, 0x25, 0x94, 0x01, 0x6d, 0x20, + 0xdf, 0x30, 0x82, 0xf8, 0xbb, 0x34, 0x47, 0x42, + 0x50, 0xe9, 0x41, 0xa7, 0x33, 0xa5]) + + factory = config.create_default_mle_message_factory(master_key=config.DEFAULT_MASTER_KEY) + + # WHEN + actual_mle_message = factory.parse(io.BytesIO(data), message_info) + + # THEN + self.assertTrue(isinstance(actual_mle_message, mle.MleMessageSecured)) + + self.assertEqual(8, actual_mle_message.command.type) + + self.assertEqual(mle.SourceAddress(address=0x9400), actual_mle_message.command.tlvs[0]) + + self.assertEqual(mle.LeaderData( + partition_id=0x06d014ca, + weighting=64, + data_version=131, + stable_data_version=168, + leader_router_id=37 + ), actual_mle_message.command.tlvs[1]) + + self.assertEqual(mle.NetworkData(tlvs=[ + network_data.Prefix( + domain_id=0, + prefix_length=64, + prefix=bytearray([0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34]), + sub_tlvs=[ + network_data.LowpanId(c=1, cid=1, context_length=64, stable=1), + network_data.BorderRouter(border_router_16=37888, prf=0, p=1, + s=1, d=0, c=0, r=1, o=1, n=0, stable=1) + ], + stable=1 + ) + ]), actual_mle_message.command.tlvs[2]) + + self.assertEqual(bytearray(data[-4:]), actual_mle_message.mic) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/scripts/thread-cert/test_network_data.py b/tests/scripts/thread-cert/test_network_data.py new file mode 100644 index 000000000..b031620e2 --- /dev/null +++ b/tests/scripts/thread-cert/test_network_data.py @@ -0,0 +1,934 @@ +import io +import random +import struct +import unittest + +import common +import config +import network_data + + +def convert_route_to_bytearray(route): + return struct.pack(">HB", route.border_router_16, ((route.prf & 0x03) << 6)) + + +def convert_routes_to_bytearray(routes): + data = bytearray() + for route in routes: + data += convert_route_to_bytearray(route) + + return data + + +def convert_has_route_to_bytearray(has_route): + return convert_routes_to_bytearray(has_route.routes) + + +def convert_border_router_to_bytearray(border_router): + data = struct.pack(">HBB", border_router.border_router_16, + (border_router.o & 0x01) | + ((border_router.r & 0x01) << 1) | + ((border_router.c & 0x01) << 2) | + ((border_router.d & 0x01) << 3) | + ((border_router.s & 0x01) << 4) | + ((border_router.p & 0x01) << 5) | + ((border_router.prf & 0x03) << 6), + ((border_router.n & 0x01) << 7)) + + return data + + +def convert_lowpan_id_to_bytearray(lowpan_id): + return bytearray([lowpan_id.cid | (lowpan_id.c << 4), lowpan_id.context_length]) + + +def convert_prefix_sub_tlvs_to_bytearray(sub_tlvs): + data = bytearray() + + for sub_tlv in sub_tlvs: + if isinstance(sub_tlv, network_data.HasRoute): + value = convert_has_route_to_bytearray(sub_tlv) + _type = sub_tlv.stable | ((0 & 0x7f) << 1) + + elif isinstance(sub_tlv, network_data.BorderRouter): + value = convert_border_router_to_bytearray(sub_tlv) + _type = sub_tlv.stable | ((2 & 0x7f) << 1) + + elif isinstance(sub_tlv, network_data.LowpanId): + value = convert_lowpan_id_to_bytearray(sub_tlv) + _type = sub_tlv.stable | ((3 & 0x7f) << 1) + + else: + raise ValueError + + data += bytearray([_type, len(value)]) + value + + return data + + +def convert_server_to_bytearray(server): + return struct.pack(">H", server.server_16) + server.server_data + + +def convert_service_sub_tlvs_to_bytearray(sub_tlvs): + data = bytearray() + + for sub_tlv in sub_tlvs: + if isinstance(sub_tlv, network_data.Server): + value = convert_server_to_bytearray(sub_tlv) + _type = sub_tlv.stable | ((6 & 0x7f) << 1) + + else: + raise ValueError + + data += bytearray([_type, len(value)]) + value + + return data + + +def convert_service_to_bytearray(service): + return struct.pack(">BLB", ((service.t & 0x01) << 7) | ((service.id) & 0x0f), + service.enterprise_number, + service.service_data_length) + \ + service.service_data + \ + convert_service_sub_tlvs_to_bytearray(service.sub_tlvs) + + +def any_border_router_16(): + return random.getrandbits(16) + + +def any_prf(): + return random.getrandbits(2) + + +def any_route(): + return network_data.Route(any_border_router_16(), any_prf()) + + +def any_routes(count=None): + if count is None: + count = random.randint(0, 16) + + return [any_route() for _ in xrange(6)] + + +def any_has_route(): + return network_data.HasRoute(any_routes(), any_stable()) + + +def any_domain_id(): + return random.getrandbits(8) + + +def any_prefix_length(): + return random.randint(1, 16) + + +def any_prefix(prefix_length=None): + if prefix_length is None: + prefix_length = any_prefix_length() + + return bytearray([random.getrandbits(8) for _ in xrange(prefix_length / 8)]) + + +def any_p(): + return random.getrandbits(1) + + +def any_s(): + return random.getrandbits(1) + + +def any_d(): + return random.getrandbits(1) + + +def any_c(): + return random.getrandbits(1) + + +def any_r(): + return random.getrandbits(1) + + +def any_o(): + return random.getrandbits(1) + + +def any_n(): + return random.getrandbits(1) + + +def any_cid(): + return random.getrandbits(4) + + +def any_context_length(): + return random.getrandbits(8) + + +def any_border_router(): + return network_data.BorderRouter(any_border_router_16(), any_prf(), any_p(), any_s(), any_d(), any_c(), any_r(), any_o(), any_n(), any_stable()) + + +def any_lowpan_id(): + return network_data.LowpanId(any_c(), any_cid(), any_context_length(), any_stable()) + + +def any_prefix_sub_tlvs(): + creator = [ + any_has_route, + any_border_router, + any_lowpan_id + ] + + sub_tlvs = [] + + for _id in xrange(random.randint(0, 16)): + c = random.choice(creator) + sub_tlvs.append(c()) + + return sub_tlvs + + +def any_t(): + return random.getrandbits(1) + + +def any_id(): + return random.getrandbits(4) + + +def any_enterprise_number(): + return random.getrandbits(32) + + +def any_service_data_length(): + return random.getrandbits(8) + + +def any_service_data(data_length=None): + if data_length is None: + data_length = random.randint(0, 16) + + return bytearray([random.getrandbits(8) for _ in xrange(data_length)]) + + +def any_server_16(): + return random.getrandbits(16) + + +def any_server_data(data_length=None): + if data_length is None: + data_length = random.randint(0, 32) + + return bytearray([random.getrandbits(8) for _ in xrange(data_length)]) + + +def any_server(): + return network_data.Server(any_server_16(), any_server_data(), any_stable()) + + +def any_service_sub_tlvs(): + creator = [ + any_server + ] + + sub_tlvs = [] + + for _id in xrange(random.randint(0, 16)): + c = random.choice(creator) + sub_tlvs.append(c()) + + return sub_tlvs + + +def any_stable(): + return random.getrandbits(1) + + +class TestRoute(unittest.TestCase): + + def test_should_return_border_router_16_value_when_border_router_16_property_is_called(self): + # GIVEN + border_router_16 = any_border_router_16() + + route = network_data.Route(border_router_16, any_prf()) + + # WHEN + actual_border_router_16 = route.border_router_16 + + # THEN + self.assertEqual(border_router_16, actual_border_router_16) + + def test_should_return_prf_value_when_prf_property_is_called(self): + # GIVEN + prf = any_prf() + + route = network_data.Route(any_border_router_16(), prf) + + # WHEN + actual_prf = route.prf + + # THEN + self.assertEqual(prf, actual_prf) + + +class TestRouteFactory(unittest.TestCase): + + def test_should_create_Route_from_bytearray_when_parse_method_is_called(self): + # GIVEN + border_router_16 = any_border_router_16() + prf = any_prf() + + factory = network_data.RouteFactory() + + data = convert_route_to_bytearray(network_data.Route(border_router_16, prf)) + + # WHEN + actual_route = factory.parse(io.BytesIO(data), None) + + # THEN + self.assertTrue(isinstance(actual_route, network_data.Route)) + self.assertEqual(border_router_16, actual_route.border_router_16) + self.assertEqual(prf, actual_route.prf) + + +class TestRoutesFactory(unittest.TestCase): + + def test_should_create_Route_list_from_bytearray_when_parse_method_is_called(self): + # GIVEN + routes = any_routes() + + factory = network_data.RoutesFactory(network_data.RouteFactory()) + + data = convert_routes_to_bytearray(routes) + # WHEN + actual_routes = factory.parse(io.BytesIO(data), None) + + # THEN + self.assertTrue(isinstance(actual_routes, list)) + self.assertEqual(routes, actual_routes) + + +class TestHasRoute(unittest.TestCase): + + def test_should_return_routes_value_when_routes_property_is_called(self): + # GIVEN + routes = any_routes() + + has_route = network_data.HasRoute(routes, any_stable()) + + # WHEN + actual_routes = has_route.routes + + # THEN + self.assertEqual(routes, actual_routes) + + def test_should_return_stable_value_when_stable_property_is_called(self): + # GIVEN + stable = any_stable() + + has_route = network_data.HasRoute(any_routes(), stable) + + # WHEN + actual_stable = has_route.stable + + # THEN + self.assertEqual(stable, actual_stable) + + +class TestHasRouteFactory(unittest.TestCase): + + def test_should_create_HasRoute_from_bytearray_when_parse_method_is_called(self): + # GIVEN + routes = any_routes() + stable = any_stable() + + factory = network_data.HasRouteFactory(network_data.RoutesFactory(network_data.RouteFactory())) + + data = convert_routes_to_bytearray(routes) + + message_info = common.MessageInfo() + message_info.stable = stable + + # WHEN + actual_has_route = factory.parse(io.BytesIO(data), message_info) + + # THEN + self.assertTrue(isinstance(actual_has_route, network_data.HasRoute)) + self.assertEqual(routes, actual_has_route.routes) + self.assertEqual(stable, actual_has_route.stable) + + +class TestPrefix(unittest.TestCase): + + def test_should_return_domain_id_value_when_domain_id_property_is_called(self): + # GIVEN + domain_id = any_domain_id() + + prefix = network_data.Prefix(domain_id, any_prefix_length(), any_prefix(), any_prefix_sub_tlvs(), any_stable()) + + # WHEN + actual_domain_id = prefix.domain_id + + # THEN + self.assertEqual(domain_id, actual_domain_id) + + def test_should_return_prefix_length_value_when_prefix_length_property_is_called(self): + # GIVEN + prefix_length = any_prefix_length() + + prefix = network_data.Prefix(any_domain_id(), prefix_length, any_prefix(), any_prefix_sub_tlvs(), any_stable()) + + # WHEN + actual_prefix_length = prefix.prefix_length + + # THEN + self.assertEqual(prefix_length, actual_prefix_length) + + def test_should_return_prefix_value_when_prefix_property_is_called(self): + # GIVEN + prefix = any_prefix() + + prefix_obj = network_data.Prefix(any_domain_id(), any_prefix_length(), + prefix, any_prefix_sub_tlvs(), any_stable()) + + # WHEN + actual_prefix = prefix_obj.prefix + + # THEN + self.assertEqual(prefix, actual_prefix) + + def test_should_return_sub_tlvs_value_when_sub_tlvs_property_is_called(self): + # GIVEN + sub_tlvs = any_prefix_sub_tlvs() + + prefix_obj = network_data.Prefix(any_domain_id(), any_prefix_length(), any_prefix(), sub_tlvs, any_stable()) + + # WHEN + actual_sub_tlvs = prefix_obj.sub_tlvs + + # THEN + self.assertEqual(sub_tlvs, actual_sub_tlvs) + + def test_should_return_stable_value_when_stable_property_is_called(self): + # GIVEN + stable = any_stable() + + prefix_obj = network_data.Prefix(any_domain_id(), any_prefix_length(), + any_prefix(), any_prefix_sub_tlvs(), stable) + + # WHEN + actual_stable = prefix_obj.stable + + # THEN + self.assertEqual(stable, actual_stable) + + +class TestPrefixSubTlvsFactory(unittest.TestCase): + + def test_should_create_SubTlvs_from_bytearray_when_parse_method_is_called(self): + # GIVEN + sub_tlvs = any_prefix_sub_tlvs() + + factory = network_data.PrefixSubTlvsFactory(config.create_default_network_data_prefix_sub_tlvs_factories()) + + data = convert_prefix_sub_tlvs_to_bytearray(sub_tlvs) + + # WHEN + actual_sub_tlvs = factory.parse(io.BytesIO(data), common.MessageInfo()) + + # THEN + self.assertTrue(isinstance(actual_sub_tlvs, list)) + self.assertEqual(sub_tlvs, actual_sub_tlvs) + + +class TestPrefixFactory(unittest.TestCase): + + def test_should_create_Prefix_from_bytearray_when_parse_method_is_called(self): + # GIVEN + domain_id = any_domain_id() + prefix_length = any_prefix_length() + prefix = any_prefix(prefix_length) + sub_tlvs = any_prefix_sub_tlvs() + + factory = network_data.PrefixFactory(config.create_default_network_data_prefix_sub_tlvs_factory()) + + data = bytearray([domain_id, prefix_length]) + prefix + convert_prefix_sub_tlvs_to_bytearray(sub_tlvs) + + message_info = common.MessageInfo() + + # WHEN + actual_prefix = factory.parse(io.BytesIO(data), message_info) + + # THEN + self.assertTrue(isinstance(actual_prefix, network_data.Prefix)) + self.assertEqual(domain_id, actual_prefix.domain_id) + self.assertEqual(prefix_length, actual_prefix.prefix_length) + self.assertEqual(prefix, actual_prefix.prefix) + self.assertEqual(sub_tlvs, actual_prefix.sub_tlvs) + + +class TestBorderRouter(unittest.TestCase): + + def test_should_return_border_router_16_value_when_border_router_16_property_is_called(self): + # GIVEN + border_router_16 = any_border_router_16() + + border_router = network_data.BorderRouter(border_router_16, any_prf( + ), any_p(), any_s(), any_d(), any_c(), any_r(), any_o(), any_n(), any_stable()) + + # WHEN + actual_border_router_16 = border_router.border_router_16 + + # THEN + self.assertEqual(border_router_16, actual_border_router_16) + + def test_should_return_prf_value_when_prf_property_is_called(self): + # GIVEN + prf = any_prf() + + border_router = network_data.BorderRouter(any_border_router_16( + ), prf, any_p(), any_s(), any_d(), any_c(), any_r(), any_o(), any_n(), any_stable()) + + # WHEN + actual_prf = border_router.prf + + # THEN + self.assertEqual(prf, actual_prf) + + def test_should_return_p_value_when_p_property_is_called(self): + # GIVEN + p = any_p() + + border_router = network_data.BorderRouter(any_border_router_16(), any_prf( + ), p, any_s(), any_d(), any_c(), any_r(), any_o(), any_n(), any_stable()) + + # WHEN + actual_p = border_router.p + + # THEN + self.assertEqual(p, actual_p) + + def test_should_return_s_value_when_s_property_is_called(self): + # GIVEN + s = any_s() + + border_router = network_data.BorderRouter(any_border_router_16(), any_prf( + ), any_p(), s, any_d(), any_c(), any_r(), any_o(), any_n(), any_stable()) + + # WHEN + actual_s = border_router.s + + # THEN + self.assertEqual(s, actual_s) + + def test_should_return_d_value_when_d_property_is_called(self): + # GIVEN + d = any_d() + + border_router = network_data.BorderRouter(any_border_router_16(), any_prf( + ), any_p(), any_s(), d, any_c(), any_r(), any_o(), any_n(), any_stable()) + + # WHEN + actual_d = border_router.d + + # THEN + self.assertEqual(d, actual_d) + + def test_should_return_c_value_when_c_property_is_called(self): + # GIVEN + c = any_c() + + border_router = network_data.BorderRouter(any_border_router_16(), any_prf( + ), any_p(), any_s(), any_d(), c, any_r(), any_o(), any_n(), any_stable()) + + # WHEN + actual_c = border_router.c + + # THEN + self.assertEqual(c, actual_c) + + def test_should_return_r_value_when_r_property_is_called(self): + # GIVEN + r = any_r() + + border_router = network_data.BorderRouter(any_border_router_16(), any_prf( + ), any_p(), any_s(), any_d(), any_c(), r, any_o(), any_n(), any_stable()) + + # WHEN + actual_r = border_router.r + + # THEN + self.assertEqual(r, actual_r) + + def test_should_return_o_value_when_o_property_is_called(self): + # GIVEN + o = any_o() + + border_router = network_data.BorderRouter(any_border_router_16(), any_prf( + ), any_p(), any_s(), any_d(), any_c(), any_r(), o, any_n(), any_stable()) + + # WHEN + actual_o = border_router.o + + # THEN + self.assertEqual(o, actual_o) + + def test_should_return_n_value_when_n_property_is_called(self): + # GIVEN + n = any_n() + + border_router = network_data.BorderRouter(any_border_router_16(), any_prf( + ), any_p(), any_s(), any_d(), any_c(), any_r(), any_o(), n, any_stable()) + + # WHEN + actual_n = border_router.n + + # THEN + self.assertEqual(n, actual_n) + + def test_should_return_stable_value_when_stable_property_is_called(self): + # GIVEN + stable = any_stable() + + border_router = network_data.BorderRouter(any_border_router_16(), any_prf( + ), any_p(), any_s(), any_d(), any_c(), any_r(), any_o(), any_n(), stable) + + # WHEN + actual_stable = border_router.stable + + # THEN + self.assertEqual(stable, actual_stable) + + +class TestBorderRouterFactory(unittest.TestCase): + + def test_should_create_BorderRouter_from_bytearray_when_parse_method_is_called(self): + # GIVEN + border_router_16 = any_border_router_16() + prf = any_prf() + p = any_p() + s = any_s() + d = any_d() + c = any_c() + r = any_r() + o = any_o() + n = any_n() + stable = any_stable() + + factory = network_data.BorderRouterFactory() + + data = convert_border_router_to_bytearray( + network_data.BorderRouter(border_router_16, prf, p, s, d, c, r, o, n, stable)) + + message_info = common.MessageInfo() + message_info.stable = stable + + # WHEN + actual_border_router = factory.parse(io.BytesIO(data), message_info) + + # THEN + self.assertTrue(isinstance(actual_border_router, network_data.BorderRouter)) + self.assertEqual(border_router_16, actual_border_router.border_router_16) + self.assertEqual(prf, actual_border_router.prf) + self.assertEqual(p, actual_border_router.p) + self.assertEqual(s, actual_border_router.s) + self.assertEqual(d, actual_border_router.d) + self.assertEqual(c, actual_border_router.c) + self.assertEqual(r, actual_border_router.r) + self.assertEqual(o, actual_border_router.o) + self.assertEqual(n, actual_border_router.n) + self.assertEqual(stable, actual_border_router.stable) + + +class TestLowpanId(unittest.TestCase): + + def test_should_return_c_value_when_c_property_is_called(self): + # GIVEN + c = any_c() + + lowpan_id = network_data.LowpanId(c, any_cid(), any_context_length(), any_stable()) + + # WHEN + actual_c = lowpan_id.c + + # THEN + self.assertEqual(c, actual_c) + + def test_should_return_cid_value_when_cid_property_is_called(self): + # GIVEN + cid = any_cid() + + lowpan_id = network_data.LowpanId(any_c(), cid, any_context_length(), any_stable()) + + # WHEN + actual_cid = lowpan_id.cid + + # THEN + self.assertEqual(cid, actual_cid) + + def test_should_return_context_length_value_when_context_length_property_is_called(self): + # GIVEN + context_length = any_context_length() + + lowpan_id = network_data.LowpanId(any_c(), any_cid(), context_length, any_stable()) + + # WHEN + actual_context_length = lowpan_id.context_length + + # THEN + self.assertEqual(context_length, actual_context_length) + + def test_should_return_stable_value_when_stable_property_is_called(self): + # GIVEN + stable = any_stable() + + lowpan_id = network_data.LowpanId(any_c(), any_cid(), any_context_length(), stable) + + # WHEN + actual_stable = lowpan_id.stable + + # THEN + self.assertEqual(stable, actual_stable) + + +class TestLowpanIdFactory(unittest.TestCase): + + def test_should_create_LowpanId_from_bytearray_when_parse_method_is_called(self): + # GIVEN + c = any_c() + cid = any_cid() + context_length = any_context_length() + stable = any_stable() + + factory = network_data.LowpanIdFactory() + + data = convert_lowpan_id_to_bytearray(network_data.LowpanId(c, cid, context_length, stable)) + + message_info = common.MessageInfo() + message_info.stable = stable + + # WHEN + actual_lowpan_id = factory.parse(io.BytesIO(data), message_info) + + # THEN + self.assertTrue(isinstance(actual_lowpan_id, network_data.LowpanId)) + self.assertEqual(c, actual_lowpan_id.c) + self.assertEqual(cid, actual_lowpan_id.cid) + self.assertEqual(context_length, actual_lowpan_id.context_length) + + +class TestService(unittest.TestCase): + + def test_should_return_t_value_when_t_property_is_called(self): + # GIVEN + t = any_t() + + service = network_data.Service(t, any_id(), any_enterprise_number(), any_service_data_length(), + any_service_data(), any_service_sub_tlvs(), any_stable()) + + # WHEN + actual_t = service.t + + # THEN + self.assertEqual(t, actual_t) + + def test_should_return_id_value_when_id_property_is_called(self): + # GIVEN + _id = any_id() + + service = network_data.Service(any_t(), _id, any_enterprise_number(), any_service_data_length(), + any_service_data(), any_service_sub_tlvs(), any_stable()) + + # WHEN + actual_id = service.id + + # THEN + self.assertEqual(_id, actual_id) + + def test_should_return_enterprise_number_value_when_enterprise_number_property_is_called(self): + # GIVEN + enterprise_number = any_enterprise_number() + + service = network_data.Service(any_t(), any_id(), enterprise_number, any_service_data_length(), + any_service_data(), any_service_sub_tlvs(), any_stable()) + + # WHEN + actual_enterprise_number = service.enterprise_number + + # THEN + self.assertEqual(enterprise_number, actual_enterprise_number) + + def test_should_return_service_data_length_value_when_service_data_length_property_is_called(self): + # GIVEN + service_data_length = any_service_data_length() + + service = network_data.Service(any_t(), any_id(), any_enterprise_number(), service_data_length, + any_service_data(), any_service_sub_tlvs(), any_stable()) + + # WHEN + actual_service_data_length = service.service_data_length + + # THEN + self.assertEqual(service_data_length, actual_service_data_length) + + def test_should_return_service_data_value_when_service_data_property_is_called(self): + # GIVEN + service_data = any_service_data() + + service = network_data.Service(any_t(), any_id(), any_enterprise_number(), any_service_data_length(), + service_data, any_service_sub_tlvs(), any_stable()) + + # WHEN + actual_service_data = service.service_data + + # THEN + self.assertEqual(service_data, actual_service_data) + + def test_should_return_sub_tlvs_value_when_sub_tlvs_property_is_called(self): + # GIVEN + sub_tlvs = any_service_sub_tlvs() + + service = network_data.Service(any_t(), any_id(), any_enterprise_number(), any_service_data_length(), + any_service_data(), sub_tlvs, any_stable()) + + # WHEN + actual_sub_tlvs = service.sub_tlvs + + # THEN + self.assertEqual(sub_tlvs, actual_sub_tlvs) + + def test_should_return_stable_value_when_stable_property_is_called(self): + # GIVEN + stable = any_stable() + + service = network_data.Service(any_t(), any_id(), any_enterprise_number(), any_service_data_length(), + any_service_data(), any_service_sub_tlvs(), stable) + + # WHEN + actual_stable = service.stable + + # THEN + self.assertEqual(stable, actual_stable) + + +class TestServiceSubTlvsFactory(unittest.TestCase): + + def test_should_create_SubTlvs_from_bytearray_when_parse_method_is_called(self): + # GIVEN + sub_tlvs = any_service_sub_tlvs() + + factory = network_data.ServiceSubTlvsFactory(config.create_default_network_data_service_sub_tlvs_factories()) + + data = convert_service_sub_tlvs_to_bytearray(sub_tlvs) + + # WHEN + actual_sub_tlvs = factory.parse(io.BytesIO(data), common.MessageInfo()) + + # THEN + self.assertTrue(isinstance(actual_sub_tlvs, list)) + self.assertEqual(sub_tlvs, actual_sub_tlvs) + + +class TestServiceFactory(unittest.TestCase): + + def test_should_create_Service_from_bytearray_when_parse_method_is_called(self): + # GIVEN + t = any_t() + _id = any_id() + enterprise_number = any_enterprise_number() + service_data_length = any_service_data_length() + service_data = any_service_data(service_data_length) + sub_tlvs = any_service_sub_tlvs() + stable = any_stable() + + factory = network_data.ServiceFactory(config.create_default_network_data_service_sub_tlvs_factory()) + + data = convert_service_to_bytearray(network_data.Service( + t, _id, enterprise_number, service_data_length, service_data, sub_tlvs, stable)) + + message_info = common.MessageInfo() + message_info.stable = stable + + # WHEN + actual_service = factory.parse(io.BytesIO(data), message_info) + + # THEN + self.assertTrue(isinstance(actual_service, network_data.Service)) + self.assertEqual(t, actual_service.t) + self.assertEqual(_id, actual_service.id) + self.assertEqual(enterprise_number, actual_service.enterprise_number) + self.assertEqual(service_data_length, actual_service.service_data_length) + self.assertEqual(service_data, actual_service.service_data) + self.assertEqual(sub_tlvs, actual_service.sub_tlvs) + + +class TestServer(unittest.TestCase): + + def test_should_return_server_16_value_when_server_16_property_is_called(self): + # GIVEN + server_16 = any_server_16() + + server = network_data.Server(server_16, any_server_data(), any_stable()) + + # WHEN + actual_server_16 = server.server_16 + + # THEN + self.assertEqual(server_16, actual_server_16) + + def test_should_return_server_data_value_when_server_data_property_is_called(self): + # GIVEN + server_data = any_server_data() + + server = network_data.Server(any_server_16(), server_data, any_stable()) + + # WHEN + actual_server_data = server.server_data + + # THEN + self.assertEqual(server_data, actual_server_data) + + def test_should_return_stable_value_when_stable_property_is_called(self): + # GIVEN + stable = any_stable() + + server = network_data.Server(any_server_16(), any_server_data(), stable) + + # WHEN + actual_stable = server.stable + + # THEN + self.assertEqual(stable, actual_stable) + + +class TestServerFactory(unittest.TestCase): + + def test_should_create_Server_from_bytearray_when_parse_method_is_called(self): + # GIVEN + server_16 = any_server_16() + server_data = any_server_data() + stable = any_stable() + + factory = network_data.ServerFactory() + + data = convert_server_to_bytearray(network_data.Server(server_16, server_data, stable)) + + message_info = common.MessageInfo() + message_info.stable = stable + + # WHEN + actual_server = factory.parse(io.BytesIO(data), message_info) + + # THEN + self.assertTrue(isinstance(actual_server, network_data.Server)) + self.assertEqual(server_16, actual_server.server_16) + self.assertEqual(server_data, actual_server.server_data) + self.assertEqual(stable, actual_server.stable) + + +if __name__ == "__main__": + unittest.main()