Add CoAP support to thread-cert. (#1063)

This commit is contained in:
Przemysław Fierek
2016-12-12 10:10:52 -08:00
committed by Jonathan Hui
parent b38faa8fa0
commit bc8b1a4cd1
30 changed files with 2476 additions and 231 deletions
@@ -32,6 +32,7 @@ import unittest
import config
import mle
import network_layer
import node
LEADER = 1
@@ -136,6 +137,20 @@ class Cert_5_1_01_RouterAttach(unittest.TestCase):
msg.assertMleMessageContainsOptionalTlv(mle.Route64)
msg.assertMleMessageContainsOptionalTlv(mle.AddressRegistration)
# 6 - Router
msg = router_messages.next_coap_message("0.02")
msg.assertCoapMessageRequestUriPath("/a/as")
msg.assertCoapMessageContainsTlv(network_layer.MacExtendedAddress)
msg.assertCoapMessageContainsTlv(network_layer.Status)
# 7 - Leader
msg = leader_messages.next_coap_message("2.04")
msg.assertCoapMessageContainsTlv(network_layer.Status)
msg.assertCoapMessageContainsOptionalTlv(network_layer.RouterMask)
status_tlv = msg.get_coap_message_tlv(network_layer.Status)
self.assertEqual(network_layer.StatusValues.SUCCESS, status_tlv.status)
# 8 - Router
msg = router_messages.next_mle_message(mle.CommandType.LINK_REQUEST)
msg.assertMleMessageContainsTlv(mle.SourceAddress)
@@ -123,12 +123,20 @@ class Cert_5_1_02_ChildAddressTimeout(unittest.TestCase):
# 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)
msg = router1_messages.next_coap_message("0.02")
msg.assertCoapMessageRequestUriPath("/a/as")
msg = leader_messages.next_coap_message("2.04")
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)
@@ -139,6 +147,26 @@ class Cert_5_1_02_ChildAddressTimeout(unittest.TestCase):
sed_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST)
router1_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE)
# 3 - Leader
msg = leader_messages.next_coap_message("0.02")
msg.assertCoapMessageRequestUriPath("/a/aq")
msg = leader_messages.next_coap_message("0.02")
msg.assertCoapMessageRequestUriPath("/a/aq")
# 4 - Router1
msg = router1_messages.does_not_contain_coap_message()
# 6 - Leader
msg = leader_messages.next_coap_message("0.02")
msg.assertCoapMessageRequestUriPath("/a/aq")
msg = leader_messages.next_coap_message("0.02")
msg.assertCoapMessageRequestUriPath("/a/aq")
# 7 - Router1
msg = router1_messages.does_not_contain_coap_message()
if __name__ == '__main__':
unittest.main()
@@ -32,6 +32,7 @@ import unittest
import config
import mle
import network_layer
import node
LEADER = 1
@@ -110,14 +111,45 @@ class Cert_5_1_03_RouterAddressReallocation(unittest.TestCase):
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)
msg = router1_messages.next_coap_message("0.02")
msg.assertCoapMessageRequestUriPath("/a/as")
msg = leader_messages.next_coap_message("2.04")
router1_messages.next_mle_message(mle.CommandType.ADVERTISEMENT)
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)
# Leader or Router1 can be parent of Router2
if leader_messages.contains_mle_message(mle.CommandType.CHILD_ID_RESPONSE):
leader_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE)
msg = router2_messages.next_coap_message("0.02")
msg.assertCoapMessageRequestUriPath("/a/as")
msg = leader_messages.next_coap_message("2.04")
elif router1_messages.contains_mle_message(mle.CommandType.CHILD_ID_RESPONSE):
router1_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE)
msg = router2_messages.next_coap_message("0.02")
msg.assertCoapMessageRequestUriPath("/a/as")
msg = router1_messages.next_coap_message("0.02")
msg.assertCoapMessageRequestUriPath("/a/as")
msg = leader_messages.next_coap_message("2.04")
msg = router1_messages.next_coap_message("2.04")
# 5 - Router1
# Router1 make two attempts to reconnect to its current Partition.
for _ in range(2):
@@ -158,6 +190,16 @@ class Cert_5_1_03_RouterAddressReallocation(unittest.TestCase):
msg.assertMleMessageContainsTlv(mle.TlvRequest)
msg.assertMleMessageDoesNotContainTlv(mle.AddressRegistration)
# 8 - Router1
msg = router1_messages.next_coap_message("0.02")
msg.assertCoapMessageRequestUriPath("/a/as")
msg.assertCoapMessageContainsTlv(network_layer.MacExtendedAddress)
msg.assertCoapMessageContainsOptionalTlv(network_layer.Rloc16)
msg.assertCoapMessageContainsTlv(network_layer.Status)
# 8 - Router2
msg = router2_messages.next_coap_message("2.04")
if __name__ == '__main__':
unittest.main()
@@ -32,6 +32,7 @@ import unittest
import config
import mle
import network_layer
import node
LEADER = 1
@@ -109,14 +110,45 @@ class Cert_5_1_04_RouterAddressReallocation(unittest.TestCase):
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)
msg = router1_messages.next_coap_message("0.02")
msg.assertCoapMessageRequestUriPath("/a/as")
msg = leader_messages.next_coap_message("2.04")
router1_messages.next_mle_message(mle.CommandType.ADVERTISEMENT)
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)
# Leader or Router1 can be parent of Router2
if leader_messages.contains_mle_message(mle.CommandType.CHILD_ID_RESPONSE):
leader_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE)
msg = router2_messages.next_coap_message("0.02")
msg.assertCoapMessageRequestUriPath("/a/as")
msg = leader_messages.next_coap_message("2.04")
elif router1_messages.contains_mle_message(mle.CommandType.CHILD_ID_RESPONSE):
router1_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE)
msg = router2_messages.next_coap_message("0.02")
msg.assertCoapMessageRequestUriPath("/a/as")
msg = router1_messages.next_coap_message("0.02")
msg.assertCoapMessageRequestUriPath("/a/as")
msg = leader_messages.next_coap_message("2.04")
msg = router1_messages.next_coap_message("2.04")
# 5 - Router1
# Router1 make two attempts to reconnect to its current Partition.
for _ in range(2):
@@ -171,6 +203,14 @@ class Cert_5_1_04_RouterAddressReallocation(unittest.TestCase):
msg.assertMleMessageContainsOptionalTlv(mle.Route64)
msg.assertMleMessageContainsOptionalTlv(mle.AddressRegistration)
# 10 - Router1
msg = router1_messages.next_coap_message("2.04")
msg.assertCoapMessageContainsTlv(network_layer.Status)
msg.assertCoapMessageContainsOptionalTlv(network_layer.RouterMask)
status_tlv = msg.get_coap_message_tlv(network_layer.Status)
self.assertEqual(network_layer.StatusValues.SUCCESS, status_tlv.status)
if __name__ == '__main__':
unittest.main()
@@ -32,6 +32,7 @@ import unittest
import config
import mle
import network_layer
import node
LEADER = 1
@@ -112,18 +113,39 @@ class Cert_5_1_05_RouterAddressTimeout(unittest.TestCase):
router1_messages.next_mle_message(mle.CommandType.PARENT_REQUEST)
router1_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST)
msg = router1_messages.next_coap_message("0.02")
msg.assertCoapMessageRequestUriPath("/a/as")
# 4 - Leader
leader_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE)
leader_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE)
msg = leader_messages.next_coap_message("2.04")
msg.assertCoapMessageContainsTlv(network_layer.Status)
msg.assertCoapMessageContainsOptionalTlv(network_layer.RouterMask)
status_tlv = msg.get_coap_message_tlv(network_layer.Status)
self.assertEqual(network_layer.StatusValues.SUCCESS, status_tlv.status)
# 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)
msg = router1_messages.next_coap_message("0.02")
msg.assertCoapMessageRequestUriPath("/a/as")
# 7 - Leader
leader_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE)
leader_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE)
msg = leader_messages.next_coap_message("2.04")
msg.assertCoapMessageContainsTlv(network_layer.Status)
msg.assertCoapMessageContainsOptionalTlv(network_layer.RouterMask)
status_tlv = msg.get_coap_message_tlv(network_layer.Status)
self.assertEqual(network_layer.StatusValues.SUCCESS, status_tlv.status)
if __name__ == '__main__':
unittest.main()
@@ -32,6 +32,7 @@ import unittest
import config
import mle
import network_layer
import node
LEADER = 1
@@ -100,6 +101,11 @@ class Cert_5_1_06_RemoveRouterId(unittest.TestCase):
router1_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST)
leader_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE)
msg = router1_messages.next_coap_message("0.02")
msg.assertCoapMessageRequestUriPath("/a/as")
msg = leader_messages.next_coap_message("2.04")
# 3 - Router1
router1_messages.next_mle_message(mle.CommandType.PARENT_REQUEST)
leader_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE)
@@ -107,6 +113,17 @@ class Cert_5_1_06_RemoveRouterId(unittest.TestCase):
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])
msg = router1_messages.next_coap_message(code="0.02", uri_path="/a/as")
msg.assertCoapMessageContainsTlv(network_layer.MacExtendedAddress)
msg.assertCoapMessageContainsTlv(network_layer.Status)
msg = leader_messages.next_coap_message("2.04")
msg.assertCoapMessageContainsTlv(network_layer.Status)
msg.assertCoapMessageContainsOptionalTlv(network_layer.RouterMask)
status_tlv = msg.get_coap_message_tlv(network_layer.Status)
self.assertEqual(network_layer.StatusValues.SUCCESS, status_tlv.status)
if __name__ == '__main__':
@@ -110,6 +110,29 @@ class Cert_5_1_10_RouterAttachLinkQuality(unittest.TestCase):
# 1 - Leader, Router1, Router2
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)
msg = router1_messages.next_coap_message("0.02")
msg.assertCoapMessageRequestUriPath("/a/as")
msg = leader_messages.next_coap_message("2.04")
router2_messages.next_mle_message(mle.CommandType.PARENT_REQUEST)
leader_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE)
router2_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST)
leader_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE)
msg = router2_messages.next_coap_message("0.02")
msg.assertCoapMessageRequestUriPath("/a/as")
msg = leader_messages.next_coap_message("2.04")
router1_messages.next_mle_message(mle.CommandType.ADVERTISEMENT)
router2_messages.next_mle_message(mle.CommandType.ADVERTISEMENT)
@@ -105,12 +105,30 @@ class Cert_5_1_11_REEDAttachLinkQuality(unittest.TestCase):
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)
router1_messages = self.sniffer.get_messages_sent_by(ROUTER1)
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.PARENT_REQUEST)
leader_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE)
reed_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)
router2_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST)
leader_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE)
msg = router2_messages.next_coap_message("0.02")
msg.assertCoapMessageRequestUriPath("/a/as")
msg = leader_messages.next_coap_message("2.04")
reed_messages.next_mle_message(mle.CommandType.ADVERTISEMENT)
router2_messages.next_mle_message(mle.CommandType.ADVERTISEMENT)
@@ -99,6 +99,11 @@ class Cert_5_1_13_RouterReset(unittest.TestCase):
router1_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST)
leader_messages.next_mle_message(mle.CommandType.CHILD_ID_RESPONSE)
msg = router1_messages.next_coap_message("0.02")
msg.assertCoapMessageRequestUriPath("/a/as")
msg = leader_messages.next_coap_message("2.04")
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)
+387
View File
@@ -0,0 +1,387 @@
#!/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 struct
from binascii import hexlify
from enum import IntEnum
class CoapMessageType(IntEnum):
CON = 0 # Confirmable
NON = 1 # Non-confirmable
ACK = 2 # Acknowledgement
RST = 3 # Reset
class CoapOptionsTypes(IntEnum):
IF_MATCH = 1
URI_HOST = 3
ETAG = 4
IF_NOT_MATCH = 5
URI_PORT = 7
LOCATION_PATH = 8
URI_PATH = 11
CONTENT_FORMAT = 12
MAX_AGE = 14
URI_QUERY = 15
ACCEPT = 17
LOCATION_QUERY = 20
PROXY_URI = 35
PROXY_SCHEME = 39
SIZE1 = 60
class CoapOptionHeader(object):
""" Class representing CoAP optiona header. """
def __init__(self, delta, length):
self._delta = delta
self._length = length
@property
def delta(self):
return self._delta
@property
def length(self):
return self._length
@property
def is_payload_marker(self):
return self.delta == 0xf and self.length == 0xf
@classmethod
def _read_extended_value(cls, data, value):
if value == 13:
return ord(data.read(1)) + 13
elif value == 14:
data.read(1)
return ord(data.read(1)) + 269
else:
return value
@classmethod
def from_bytes(cls, data):
initial_byte = ord(data.read(1))
delta = (initial_byte >> 4) & 0xf
length = initial_byte & 0xf
delta = cls._read_extended_value(data, delta)
length = cls._read_extended_value(data, length)
return cls(delta, length)
class CoapOption(object):
""" Class representing CoAP option. """
def __init__(self, _type, value):
self._type = _type
self._value = value
@property
def type(self):
return self._type
@property
def value(self):
return self._value
def __repr__(self):
return "CoapOption(type={}, value={})".format(self.type, hexlify(self.value))
class CoapOptionsFactory(object):
""" Factory that produces CoAP options. """
def parse(self, data, message_info):
options = []
_type = 0
while data.tell() < len(data.getvalue()):
option_header = CoapOptionHeader.from_bytes(data)
if option_header.is_payload_marker:
break
_type += option_header.delta
value = data.read(option_header.length)
option = CoapOption(_type, value)
options.append(option)
return options
class CoapCode(object):
""" Class representing CoAP code. """
def __init__(self, code):
self._code = code
@property
def code(self):
return self._code
@property
def _class(self):
return (self.code >> 5) & 0x7
@property
def detail(self):
return self.code & 0x1f
@classmethod
def from_class_and_detail(cls, _class, detail):
return cls(((_class & 0x7) << 5) | (detail & 0x1f))
@classmethod
def from_dotted(cls, dotted_str):
_class, detail = dotted_str.split(".")
return cls.from_class_and_detail(int(_class), int(detail))
def is_equal_dotted(self, dotted_code):
other = self.from_dotted(dotted_code)
return self.code == other.code
@property
def dotted(self):
return ".".join(["{:01d}".format(self._class), "{:02d}".format(self.detail)])
def __eq__(self, other):
if isinstance(other, int):
return self.code == other
elif isinstance(other, str):
return self.is_equal_dotted(other)
elif isinstance(other, self.__class__):
return self.code == other.code
else:
raise TypeError("Could not compare {} and {}".format(type(self), type(other)))
def __repr__(self):
return self.dotted
class CoapMessage(object):
""" Class representing CoAP message. """
def __init__(self, version, _type, code, message_id, token, options, payload, uri_path=None):
self._version = version
self._type = _type
self._code = code
self._message_id = message_id
self._token = token
self._options = options
self._payload = payload
self._uri_path = uri_path
@property
def version(self):
return self._version
@property
def type(self):
return self._type
@property
def code(self):
return self._code
@property
def message_id(self):
return self._message_id
@property
def token(self):
return self._token
@property
def tkl(self):
return len(self._token)
@property
def options(self):
return self._options
@property
def payload(self):
return self._payload
@property
def uri_path(self):
return self._uri_path
def __repr__(self):
options_str = ", ".join([repr(opt) for opt in self.options])
return "CoapMessage(version={}, type={}, code={}, message_id={}, token={}, options=[{}], payload={}, uri-path='{}')".format(
self.version, CoapMessageType.name[self.type], self.code, self.message_id, hexlify(self.token),
options_str, self.payload, self.uri_path)
class CoapMessageProxy(object):
""" Proxy class of CoAP message.
The main idea behind this class is to delay parsing payload. Due to architecture of the existing solution
it is possible to process confirmation message before a request message. In such case it is not possible
to get URI path to get proper payload parser.
"""
def __init__(self, coap_message, message_info, mid_to_uri_path_binder, uri_path_based_payload_factories):
self._coap_message = coap_message
self._message_info = message_info
self._mid_to_uri_path_binder = mid_to_uri_path_binder
self._uri_path_based_payload_factories = uri_path_based_payload_factories
@property
def version(self):
return self._coap_message.version
@property
def type(self):
return self._coap_message.type
@property
def code(self):
return self._coap_message.code
@property
def message_id(self):
return self._coap_message.message_id
@property
def token(self):
return self._coap_message.token
@property
def tkl(self):
return self._coap_message.tkl
@property
def options(self):
return self._coap_message.options
@property
def payload(self):
try:
binded_uri_path = self._mid_to_uri_path_binder.get_uri_path_for(self.message_id, self.token)
factory = self._uri_path_based_payload_factories[binded_uri_path]
return factory.parse(io.BytesIO(self._coap_message.payload), self._message_info)
except RuntimeError:
return self._coap_message.payload
@property
def uri_path(self):
return self._coap_message.uri_path
def __repr__(self):
options_str = ", ".join([repr(opt) for opt in self.options])
return "CoapMessageProxy(version={}, type={}, code={}, message_id={}, token={}, options=[{}], payload={}, uri-path='{}')".format(
self.version, self.type, self.code, self.message_id, hexlify(self.token),
options_str, self.payload, self.uri_path)
class CoapMessageIdToUriPathBinder:
""" Class binds message id and token with URI path. """
def __init__(self):
self._uri_path_binds = collections.defaultdict(collections.defaultdict)
def add_uri_path_for(self, message_id, token, uri_path):
self._uri_path_binds[message_id][hexlify(token)] = uri_path
def get_uri_path_for(self, message_id, token):
try:
return self._uri_path_binds[message_id][hexlify(token)]
except KeyError:
raise RuntimeError("Could not find URI PATH for message_id: {} and token: {}".format(
message_id, hexlify(token)))
class CoapMessageFactory(object):
""" Factory that produces CoAP messages. """
def __init__(self, options_factory, uri_path_based_payload_factories, message_id_to_uri_path_binder):
self._options_factory = options_factory
self._uri_path_based_payload_factories = uri_path_based_payload_factories
self._mid_to_uri_path_binder = message_id_to_uri_path_binder
def _uri_path_from(self, options):
uri_path_options = []
for option in options:
if option.type == CoapOptionsTypes.URI_PATH:
uri_path_options.append(option.value.decode("utf-8"))
if not uri_path_options:
return None
return "/" + "/".join(uri_path_options)
def _parse_initial_byte(self, data, message_info):
initial_byte = ord(data.read(1))
version = (initial_byte >> 6) & 0x3
_type = CoapMessageType((initial_byte >> 4) & 0x3)
token_length = initial_byte & 0xf
return version, _type, token_length
def parse(self, data, message_info):
version, _type, token_length = self._parse_initial_byte(data, message_info)
code = CoapCode(ord(data.read(1)))
message_id = struct.unpack(">H", data.read(2))[0]
token = data.read(token_length)
options = self._options_factory.parse(data, message_info)
uri_path = self._uri_path_from(options)
if uri_path is not None:
self._mid_to_uri_path_binder.add_uri_path_for(message_id, token, uri_path)
coap_message = CoapMessage(version, _type, code, message_id, token, options, data.read(), uri_path)
return CoapMessageProxy(coap_message, message_info, self._mid_to_uri_path_binder, self._uri_path_based_payload_factories)
+37 -20
View File
@@ -27,18 +27,18 @@
# POSSIBILITY OF SUCH DAMAGE.
#
from binascii import hexlify
import ipaddress
import struct
import sys
from binascii import hexlify
from enum import IntEnum
def enum(*sequential, **named):
enums = dict(list(zip(sequential, list(range(len(sequential))))), **named)
names = dict((value, key) for key, value in list(enums.items()))
enums['name'] = names
return type('Enum', (), enums)
import ipaddress
def expect_the_same_class(self, other):
if not isinstance(other, self.__class__):
raise TypeError("Expected the same class. Got {} and {}".format(type(self), type(other)))
class MessageInfo(object):
@@ -56,6 +56,9 @@ class MessageInfo(object):
self._source_ipv6 = None
self._destination_ipv6 = None
self._src_port = None
self._dst_port = None
self.stable = None
self.payload_length = 0
@@ -84,16 +87,34 @@ class MessageInfo(object):
def destination_ipv6(self, value):
self._destination_ipv6 = self._convert_value_to_ip_address(value)
@property
def src_port(self):
return self._src_port
class MacAddress(object):
@src_port.setter
def src_port(self, value):
self._src_port = value
@property
def dst_port(self):
return self._dst_port
@dst_port.setter
def dst_port(self, value):
self._dst_port = value
class MacAddressType(IntEnum):
SHORT = 0
LONG = 1
class MacAddress(object):
def __init__(self, mac_address, _type, big_endian=True):
if _type == self.SHORT:
if _type == MacAddressType.SHORT:
length = 2
elif _type == self.LONG:
elif _type == MacAddressType.LONG:
length = 8
if not big_endian:
@@ -106,10 +127,6 @@ class MacAddress(object):
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
@@ -119,9 +136,9 @@ class MacAddress(object):
return struct.unpack(">H", self._mac_address)[0]
def convert_to_iid(self):
if self._type == self.SHORT:
if self._type == MacAddressType.SHORT:
return bytearray([0x00, 0x00, 0x00, 0xff, 0xfe, 0x00]) + self._mac_address[:2]
elif self._type == self.LONG:
elif self._type == MacAddressType.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))
@@ -131,7 +148,7 @@ class MacAddress(object):
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)
return cls(eui64, MacAddressType.LONG)
@classmethod
def from_rloc16(cls, rloc16, big_endian=True):
@@ -142,10 +159,10 @@ class MacAddress(object):
else:
raise RuntimeError("Could not create MAC address from RLOC16. Invalid data type: {}".format(type(rloc16)))
return cls(mac_address, MacAddress.SHORT)
return cls(mac_address, MacAddressType.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)
return "MacAddress(mac_address=b'{}', type={})".format(hexlify(self.mac_address), MacAddressType(self._type))
+64 -25
View File
@@ -27,27 +27,28 @@
# POSSIBILITY OF SUCH DAMAGE.
#
import coap
import ipv6
import lowpan
import message
import mle
import net_crypto
import network_data
import network_layer
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(
network_data.TlvType.HAS_ROUTE: network_data.HasRouteFactory(
routes_factory=network_data.RoutesFactory(
route_factory=network_data.RouteFactory())
),
2: network_data.BorderRouterFactory(),
3: network_data.LowpanIdFactory()
network_data.TlvType.BORDER_ROUTER: network_data.BorderRouterFactory(),
network_data.TlvType.LOWPAN_ID: network_data.LowpanIdFactory()
}
@@ -58,7 +59,7 @@ def create_default_network_data_prefix_sub_tlvs_factory():
def create_default_network_data_service_sub_tlvs_factories():
return {
6: network_data.ServerFactory()
network_data.TlvType.SERVER: network_data.ServerFactory()
}
@@ -69,10 +70,10 @@ def create_default_network_data_service_sub_tlvs_factory():
def create_default_network_data_tlvs_factories():
return {
1: network_data.PrefixFactory(
network_data.TlvType.PREFIX: network_data.PrefixFactory(
sub_tlvs_factory=create_default_network_data_prefix_sub_tlvs_factory()
),
5: network_data.ServiceFactory(
network_data.TlvType.SERVICE: network_data.ServiceFactory(
sub_tlvs_factory=create_default_network_data_service_sub_tlvs_factory()
)
}
@@ -141,6 +142,46 @@ def create_default_mle_message_factory(master_key):
crypto_engine=create_default_mle_crypto_engine(master_key))
def create_deafult_network_tlvs_factories():
return {
network_layer.TlvType.TARGET_EID: network_layer.TargetEidFactory(),
network_layer.TlvType.MAC_EXTENDED_ADDRESS: network_layer.MacExtendedAddressFactory(),
network_layer.TlvType.RLOC16: network_layer.Rloc16Factory(),
network_layer.TlvType.ML_EID: network_layer.MlEidFactory(),
network_layer.TlvType.STATUS: network_layer.StatusFactory(),
network_layer.TlvType.TIME_SINCE_LAST_TRANSACTION: network_layer.TimeSinceLastTransactionFactory(),
network_layer.TlvType.ROUTER_MASK: network_layer.RouterMaskFactory(),
network_layer.TlvType.ND_OPTION: network_layer.NdOptionFactory(),
network_layer.TlvType.ND_DATA: network_layer.NdDataFactory(),
network_layer.TlvType.THREAD_NETWORK_DATA: network_layer.ThreadNetworkDataFactory(create_default_network_data_tlvs_factory),
# Routing information are distributed in a Thread network by MLE Routing TLV
# which is in fact MLE Route64 TLV. Thread specificaton v1.1. - Chapter 5.20
network_layer.TlvType.MLE_ROUTING: create_default_mle_tlv_route64_factory()
}
def create_default_network_tlvs_factory():
return network_layer.NetworkLayerTlvsFactory(
tlvs_factories=create_deafult_network_tlvs_factories())
def create_default_uri_path_based_payload_factories():
network_layer_tlvs_factory = create_default_network_tlvs_factory()
return {
"/a/as": network_layer_tlvs_factory,
"/a/aq": network_layer_tlvs_factory,
"/a/an": network_layer_tlvs_factory
}
def create_default_coap_message_factory():
return coap.CoapMessageFactory(options_factory=coap.CoapOptionsFactory(),
uri_path_based_payload_factories=create_default_uri_path_based_payload_factories(),
message_id_to_uri_path_binder=coap.CoapMessageIdToUriPathBinder())
def create_default_ipv6_hop_by_hop_options_factories():
return {
109: ipv6.MPLOptionFactory()
@@ -152,35 +193,33 @@ def create_default_ipv6_hop_by_hop_options_factory():
options_factories=create_default_ipv6_hop_by_hop_options_factories())
def create_default_ipv6_udp_dst_port_factories(master_key):
def create_default_based_on_src_dst_ports_udp_payload_factory(master_key):
mle_message_factory = create_default_mle_message_factory(master_key)
coap_message_factory = create_default_coap_message_factory()
return {
19788: mle_message_factory,
# TODO: Improve CoAP support
61631: ipv6.UDPBytesPayloadFactory(),
49152: ipv6.UDPBytesPayloadFactory(),
49153: ipv6.UDPBytesPayloadFactory(),
49154: ipv6.UDPBytesPayloadFactory()
}
return ipv6.UdpBasedOnSrcDstPortsPayloadFactory(
src_dst_port_based_payload_factories={
19788: mle_message_factory,
61631: coap_message_factory
}
)
def create_default_ipv6_icmp_body_factories():
return {
0: ipv6.ICMPv6DestinationUnreachableFactory(),
128: ipv6.ICMPv6EchoBodyFactory(),
129: ipv6.ICMPv6EchoBodyFactory()
ipv6.ICMP_DESTINATION_UNREACHABLE: ipv6.ICMPv6DestinationUnreachableFactory(),
ipv6.ICMP_ECHO_REQUEST: ipv6.ICMPv6EchoBodyFactory(),
ipv6.ICMP_ECHO_RESPONSE: ipv6.ICMPv6EchoBodyFactory()
}
def create_default_ipv6_upper_layer_factories(master_key):
return {
17: ipv6.UDPDatagramFactory(
ipv6.IPV6_NEXT_HEADER_UDP: ipv6.UDPDatagramFactory(
udp_header_factory=ipv6.UDPHeaderFactory(),
dst_port_factories=create_default_ipv6_udp_dst_port_factories(master_key)
udp_payload_factory=create_default_based_on_src_dst_ports_udp_payload_factory(master_key)
),
58: ipv6.ICMPv6Factory(
ipv6.IPV6_NEXT_HEADER_ICMP: ipv6.ICMPv6Factory(
body_factories=create_default_ipv6_icmp_body_factories()
)
}
@@ -188,7 +227,7 @@ def create_default_ipv6_upper_layer_factories(master_key):
def create_default_lowpan_extension_headers_factories():
return {
0: lowpan.LowpanHopByHopFactory(
ipv6.IPV6_NEXT_HEADER_HOP_BY_HOP: lowpan.LowpanHopByHopFactory(
hop_by_hop_options_factory=create_default_ipv6_hop_by_hop_options_factory()
)
}
@@ -196,7 +235,7 @@ def create_default_lowpan_extension_headers_factories():
def create_default_ipv6_extension_headers_factories():
return {
0: ipv6.HopByHopFactory(
ipv6.IPV6_NEXT_HEADER_HOP_BY_HOP: ipv6.HopByHopFactory(
hop_by_hop_options_factory=create_default_ipv6_hop_by_hop_options_factory())
}
+45 -21
View File
@@ -53,6 +53,7 @@ UPPER_LAYER_PROTOCOLS = [
]
# ICMP Protocol codes
ICMP_DESTINATION_UNREACHABLE = 0
ICMP_ECHO_REQUEST = 128
ICMP_ECHO_RESPONSE = 129
@@ -92,11 +93,12 @@ class PacketFactory(object):
""" Interface for classes that produce objects from data. """
def parse(self, data):
def parse(self, data, message_info):
""" Convert data to object.
Args:
data (bytes)
data (BytesIO)
message_info (MessageInfo)
"""
raise NotImplementedError
@@ -442,7 +444,7 @@ class IPv6Packet(ConvertibleToBytes):
return ipv6_packet
def __repr__(self):
return "IPv6Packet(\n\theader={})".format(self.ipv6_header)
return "IPv6Packet(header={}, upper_layer_protocol={})".format(self.ipv6_header, self.upper_layer_protocol)
class UDPHeader(ConvertibleToBytes, BuildableFromBytes):
@@ -953,34 +955,56 @@ class UDPHeaderFactory:
return UDPHeader.from_bytes(data)
class UdpBasedOnSrcDstPortsPayloadFactory:
# TODO: Unittests
""" Factory that produces UDP payload. """
def __init__(self, src_dst_port_based_payload_factories):
"""
Args:
src_dst_port_based_payload_factories (PacketFactory): Factories parse UDP payload based on source or destination port.
"""
self._factories = src_dst_port_based_payload_factories
def parse(self, data, message_info):
factory = None
if message_info.dst_port in self._factories:
factory = self._factories[message_info.dst_port]
if message_info.src_port in self._factories:
factory = self._factories[message_info.src_port]
if factory is None:
raise RuntimeError("Could not find factory to build UDP payload.")
return factory.parse(data, message_info)
class UDPDatagramFactory(PacketFactory):
# TODO: Unittests
""" 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.
"""
def __init__(self, udp_header_factory, udp_payload_factory):
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))
self._udp_payload_factory = udp_payload_factory
def parse(self, data, message_info):
udp_header = self._udp_header_factory.parse(data, message_info)
header = self._udp_header_factory.parse(data, message_info)
factory = self._get_payload_factory(udp_header.dst_port)
# Update message payload length: UDP header (8B) + payload length
message_info.payload_length += len(header) + (len(data.getvalue()) - data.tell())
message_info.payload_length += len(udp_header) + (len(data.getvalue()) - data.tell())
message_info.src_port = header.src_port
message_info.dst_port = header.dst_port
return UDPDatagram(udp_header, factory.parse(data, message_info))
payload = self._udp_payload_factory.parse(data, message_info)
return UDPDatagram(header, payload)
class ICMPv6Factory(PacketFactory):
+4 -4
View File
@@ -35,7 +35,7 @@ import io
import struct
import config
from common import MacAddress, MessageInfo
from common import MacAddress, MacAddressType, MessageInfo
from net_crypto import AuxiliarySecurityHeader, CryptoEngine, MacCryptoMaterialCreator
@@ -200,7 +200,7 @@ class MacFrame:
message_info.aux_sec_hdr_bytes = aux_sec_hdr_bytes
message_info.nonpayload_fields = non_payload_fields
message_info.mhr_bytes = mhr_bytes
if src_address.type == MacAddress.SHORT:
if src_address.type == MacAddressType.SHORT:
message_info.source_mac_address = DeviceDescriptors.get_extended(src_address).mac_address
else:
message_info.source_mac_address = src_address.mac_address
@@ -213,10 +213,10 @@ class MacFrame:
def _parse_address(self, data, mode):
if mode == MacHeader.AddressMode.SHORT:
return MacAddress(data.read(2), MacAddress.SHORT, big_endian=False)
return MacAddress(data.read(2), MacAddressType.SHORT, big_endian=False)
if mode == MacHeader.AddressMode.EXTENDED:
return MacAddress(data.read(8), MacAddress.LONG, big_endian=False)
return MacAddress(data.read(8), MacAddressType.LONG, big_endian=False)
else:
return None
+92 -12
View File
@@ -32,19 +32,22 @@ import ipaddress
import struct
import sys
import coap
import common
import ipv6
import lowpan
import mac802154
import mle
from enum import IntEnum
MessageType = common.enum("MLE",
"COAP",
"ICMP",
"ACK",
"BEACON",
"DATA")
class MessageType(IntEnum):
MLE = 0
COAP = 1
ICMP = 2
ACK = 3
BEACON = 4
DATA = 5
class Message(object):
@@ -54,6 +57,7 @@ class Message(object):
self._channel = None
self._mac_header = None
self._ipv6_packet = None
self._coap = None
self._mle = None
self._icmp = None
@@ -62,8 +66,7 @@ class Message(object):
self._type = MessageType.MLE
self._mle = udp_datagram.payload
# TODO: Initial version doesn't support CoAP
elif isinstance(udp_datagram.payload, ipv6.UDPBytesPayload):
elif isinstance(udp_datagram.payload, (coap.CoapMessage, coap.CoapMessageProxy)):
self._type = MessageType.COAP
self._coap = udp_datagram.payload
@@ -117,14 +120,14 @@ class Message(object):
self._ipv6_packet = value
self._extract_upper_layer_protocol(value.upper_layer_protocol)
@property
def coap(self):
return self._coap
@property
def mle(self):
return self._mle
@mle.setter
def mle(self, value):
self._mle = value
@property
def icmp(self):
return self._icmp
@@ -183,6 +186,50 @@ class Message(object):
print("MleMessage doesn't contain optional TLV: {}".format(tlv_class_type))
def get_coap_message_tlv(self, tlv_class_type):
if self.type != MessageType.COAP:
raise ValueError("Invalid message type. Expected CoAP message.")
for tlv in self.coap.payload:
if isinstance(tlv, tlv_class_type):
return tlv
def assertCoapMessageContainsTlv(self, tlv_class_type):
if self.type != MessageType.COAP:
raise ValueError("Invalid message type. Expected CoAP message.")
contains_tlv = False
for tlv in self.coap.payload:
if isinstance(tlv, tlv_class_type):
contains_tlv = True
break
assert(contains_tlv == True)
def assertCoapMessageContainsOptionalTlv(self, tlv_class_type):
if self.type != MessageType.COAP:
raise ValueError("Invalid message type. Expected CoAP message.")
contains_tlv = False
for tlv in self.coap.payload:
if isinstance(tlv, tlv_class_type):
contains_tlv = True
break
print("CoapMessage doesn't contain optional TLV: {}".format(tlv_class_type))
def assertCoapMessageRequestUriPath(self, uri_path):
if self.type != MessageType.COAP:
raise ValueError("Invalid message type. Expected CoAP message.")
assert(uri_path == self.coap.uri_path)
def assertCoapMessageCode(self, code):
if self.type != MessageType.COAP:
raise ValueError("Invalid message type. Expected CoAP message.")
assert(code == self.coap.code)
def assertSentToNode(self, node):
sent_to_node = False
dst_addr = self.ipv6_packet.ipv6_header.destination_address
@@ -215,6 +262,30 @@ class MessagesSet(object):
def messages(self):
return self._messages
def next_coap_message(self, code, uri_path=None, assert_enabled=True):
message = None
while self.messages:
m = self.messages.pop(0)
if m.type != MessageType.COAP:
continue
if uri_path is not None and m.coap.uri_path != uri_path:
continue
else:
if not m.coap.code.is_equal_dotted(code):
continue
message = m
break
if assert_enabled:
assert message is not None, "Could not find CoapMessage with code: {}".format(code)
return message
def next_mle_message(self, command_type, assert_enabled=True):
message = self.next_mle_message_of_one_of_command_types(command_type,)
@@ -255,6 +326,15 @@ class MessagesSet(object):
return False
def does_not_contain_coap_message(self):
for m in self.messages:
if m.type != MessageType.COAP:
continue
return False
return True
class MessageFactory:
+88 -106
View File
@@ -36,52 +36,56 @@ 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
)
from enum import IntEnum
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 CommandType(IntEnum):
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
class TlvType(IntEnum):
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):
@@ -94,8 +98,7 @@ class SourceAddress(object):
return self._address
def __eq__(self, other):
if not isinstance(other, self.__class__):
raise TypeError("Could not compare {} and {}".format(type(self), type(other)))
common.expect_the_same_class(self, other)
return self.address == other.address
@@ -135,11 +138,12 @@ class Mode(object):
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)))
common.expect_the_same_class(self, 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
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,
@@ -169,8 +173,7 @@ class Timeout(object):
return self._timeout
def __eq__(self, other):
if not isinstance(other, self.__class__):
raise TypeError("Could not compare {} and {}".format(type(self), type(other)))
common.expect_the_same_class(self, other)
return self.timeout == other.timeout
@@ -195,13 +198,12 @@ class Challenge(object):
return self._challenge
def __eq__(self, other):
if not isinstance(other, self.__class__):
raise TypeError("Could not compare {} and {}".format(type(self), type(other)))
common.expect_the_same_class(self, other)
return self.challenge == other.challenge
def __repr__(self):
return "Challenge(challenge=b'{}')".format(hexlify(self.challenge))
return "Challenge(challenge={})".format(hexlify(self.challenge))
class ChallengeFactory:
@@ -221,13 +223,12 @@ class Response(object):
return self._response
def __eq__(self, other):
if not isinstance(other, Response):
raise TypeError("Could not compare {} and {}".format(type(self), type(other)))
common.expect_the_same_class(self, other)
return self.response == other.response
def __repr__(self):
return "Response(response=b'{}')".format(hexlify(self.response))
return "Response(response={})".format(hexlify(self.response))
class ResponseFactory:
@@ -247,8 +248,7 @@ class LinkLayerFrameCounter(object):
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)))
common.expect_the_same_class(self, other)
return self.frame_counter == other.frame_counter
@@ -273,8 +273,7 @@ class MleFrameCounter(object):
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)))
common.expect_the_same_class(self, other)
return self.frame_counter == other.frame_counter
@@ -309,8 +308,7 @@ class LinkQualityAndRouteData(object):
return self._route
def __eq__(self, other):
if not isinstance(other, self.__class__):
raise TypeError("Could not compare {} and {}".format(type(self), type(other)))
common.expect_the_same_class(self, other)
return self.output == other.output and self.input == other.input and self.route == other.route
@@ -348,10 +346,10 @@ class Route64(object):
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)))
common.expect_the_same_class(self, other)
return self.id_sequence == other.id_sequence and self.router_id_mask == other.router_id_mask and \
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):
@@ -387,8 +385,7 @@ class Address16(object):
return self._address
def __eq__(self, other):
if not isinstance(other, self.__class__):
raise TypeError("Could not compare {} and {}".format(type(self), type(other)))
common.expect_the_same_class(self, other)
return self.address == other.address
@@ -433,8 +430,7 @@ class LeaderData(object):
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)))
common.expect_the_same_class(self, other)
return self.partition_id == other.partition_id and \
self.weighting == other.weighting and \
@@ -468,8 +464,7 @@ class NetworkData(object):
return self._tlvs
def __eq__(self, other):
if not isinstance(other, self.__class__):
raise TypeError("Could not compare {} and {}".format(type(self), type(other)))
common.expect_the_same_class(self, other)
return self.tlvs == other.tlvs
@@ -498,8 +493,7 @@ class TlvRequest(object):
return self._tlvs
def __eq__(self, other):
if not isinstance(other, self.__class__):
raise TypeError("Could not compare {} and {}".format(type(self), type(other)))
common.expect_the_same_class(self, other)
return self.tlvs == other.tlvs
@@ -530,8 +524,7 @@ class ScanMask(object):
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)))
common.expect_the_same_class(self, other)
return self.router == other.router and self.end_device == other.end_device
@@ -607,8 +600,7 @@ class Connectivity(object):
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)))
common.expect_the_same_class(self, other)
return self.pp == other.pp and \
self.link_quality_3 == other.link_quality_3 and \
@@ -621,7 +613,7 @@ class Connectivity(object):
self.sed_datagram_count == other.sed_datagram_count
def __repr__(self):
return "Connectivity(pp={}, \
return r"Connectivity(pp={}, \
link_quality_3={}, \
link_quality_2={}, \
link_quality_1={}, \
@@ -683,8 +675,7 @@ class LinkMargin(object):
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)))
common.expect_the_same_class(self, other)
return self.link_margin == other.link_margin
@@ -709,8 +700,7 @@ class Status(object):
return self._status
def __eq__(self, other):
if not isinstance(other, self.__class__):
raise TypeError("Could not compare {} and {}".format(type(self), type(other)))
common.expect_the_same_class(self, other)
return self.status == other.status
@@ -735,8 +725,7 @@ class Version(object):
return self._version
def __eq__(self, other):
if not isinstance(other, self.__class__):
raise TypeError("Could not compare {} and {}".format(type(self), type(other)))
common.expect_the_same_class(self, other)
return self.version == other.version
@@ -761,13 +750,12 @@ class AddressFull(object):
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)))
common.expect_the_same_class(self, other)
return self.ipv6_address == other.ipv6_address
def __repr__(self):
return "AddressFull(ipv6_address=b'{}'')".format(hexlify(self.ipv6_address))
return "AddressFull(ipv6_address={}')".format(hexlify(self.ipv6_address))
class AddressFullFactory:
@@ -793,13 +781,12 @@ class AddressCompressed(object):
return self._iid
def __eq__(self, other):
if not isinstance(other, self.__class__):
raise TypeError("Could not compare {} and {}".format(type(self), type(other)))
common.expect_the_same_class(self, 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))
return "AddressCompressed(cid={}, iid={}')".format(self.cid, hexlify(self.iid))
class AddressCompressedFactory:
@@ -820,8 +807,7 @@ class AddressRegistration(object):
return self._addresses
def __eq__(self, other):
if not isinstance(other, self.__class__):
raise TypeError("Could not compare {} and {}".format(type(self), type(other)))
common.expect_the_same_class(self, other)
return self.addresses == other.addresses
@@ -866,8 +852,7 @@ class Channel(object):
return self._channel
def __eq__(self, other):
if not isinstance(other, self.__class__):
raise TypeError("Could not compare {} and {}".format(type(self), type(other)))
common.expect_the_same_class(self, other)
return self.channel_page == other.channel_page and \
self.channel == other.channel
@@ -894,8 +879,7 @@ class PanId:
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)))
common.expect_the_same_class(self, other)
return self.pan_id == other.pan_id
@@ -930,8 +914,7 @@ class ActiveTimestamp(object):
return self._u
def __eq__(self, other):
if not isinstance(other, self.__class__):
raise TypeError("Could not compare {} and {}".format(type(self), type(other)))
common.expect_the_same_class(self, other)
return self.timestamp_seconds == other.timestamp_seconds and \
self.timestamp_ticks == other.timestamp_ticks and \
@@ -974,8 +957,7 @@ class PendingTimestamp(object):
return self._u
def __eq__(self, other):
if not isinstance(other, self.__class__):
raise TypeError("Could not compare {} and {}".format(type(self), type(other)))
common.expect_the_same_class(self, other)
return self.timestamp_seconds == other.timestamp_seconds and \
self.timestamp_ticks == other.timestamp_ticks and \
@@ -1053,7 +1035,7 @@ class MleCommand(object):
def __repr__(self):
tlvs_str = ", ".join(["{}".format(tlv) for tlv in self.tlvs])
return "MleCommand(type={}, tlvs=[{}])".format(self.type, tlvs_str)
return "MleCommand(type={}, tlvs=[{}])".format(self.type.name, tlvs_str)
class MleCommandFactory:
@@ -1078,7 +1060,7 @@ class MleCommandFactory:
raise KeyError("Could not find TLV factory. Unsupported TLV type: {}".format(_type))
def _parse_tlv(self, data, message_info):
_type = ord(data.read(1))
_type = TlvType(ord(data.read(1)))
length = self._get_length(data)
value = data.read(length)
@@ -1087,7 +1069,7 @@ class MleCommandFactory:
return factory.parse(io.BytesIO(value), message_info)
def parse(self, data, message_info):
cmd_type = ord(data.read(1))
cmd_type = CommandType(ord(data.read(1)))
tlvs = []
while data.tell() < len(data.getvalue()):
+21 -15
View File
@@ -32,6 +32,19 @@ import math
import struct
from binascii import hexlify
from enum import IntEnum
import common
class TlvType(IntEnum):
HAS_ROUTE = 0
PREFIX = 1
BORDER_ROUTER = 2
LOWPAN_ID = 3
COMMISSIONING = 4
SERVICE = 5
SERVER = 6
class NetworkData(object):
@@ -92,8 +105,7 @@ class Route(object):
return self._prf
def __eq__(self, other):
if not isinstance(other, self.__class__):
raise TypeError("Could not compare {} and {}".format(type(self), type(other)))
common.expect_the_same_class(self, other)
return self.border_router_16 == other.border_router_16 and self.prf == other.prf
@@ -139,8 +151,7 @@ class HasRoute(NetworkData):
return self._routes
def __eq__(self, other):
if not isinstance(other, self.__class__):
raise TypeError("Could not compare {} and {}".format(type(self), type(other)))
common.expect_the_same_class(self, other)
return self.routes == other.routes
@@ -186,8 +197,7 @@ class Prefix(NetworkData):
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)))
common.expect_the_same_class(self, other)
return self.domain_id == other.domain_id and \
self.prefix_length == other.prefix_length and \
@@ -277,8 +287,7 @@ class BorderRouter(NetworkData):
return self._n
def __eq__(self, other):
if not isinstance(other, self.__class__):
raise TypeError("Could not compare {} and {}".format(type(self), type(other)))
common.expect_the_same_class(self, other)
return self.border_router_16 == other.border_router_16 and \
self.prf == other.prf and \
@@ -336,8 +345,7 @@ class LowpanId(NetworkData):
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)))
common.expect_the_same_class(self, other)
return self.c == other.c and \
self.cid == other.cid and \
@@ -411,8 +419,7 @@ class Service(NetworkData):
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)))
common.expect_the_same_class(self, other)
return self.t == other.t and \
self.id == other.id and \
@@ -468,14 +475,13 @@ class Server(NetworkData):
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)))
common.expect_the_same_class(self, 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(
return "LowpanId(stable={}, server_16={}, server_data=b'{}')".format(
self.stable, self.server_16, hexlify(self.server_data))
+322
View File
@@ -0,0 +1,322 @@
#!/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 struct
from binascii import hexlify
from enum import IntEnum
import common
class TlvType(IntEnum):
TARGET_EID = 0
MAC_EXTENDED_ADDRESS = 1
RLOC16 = 2
ML_EID = 3
STATUS = 4
TIME_SINCE_LAST_TRANSACTION = 6
ROUTER_MASK = 7
ND_OPTION = 8
ND_DATA = 9
THREAD_NETWORK_DATA = 10
MLE_ROUTING = 11
class StatusValues(IntEnum):
SUCCESS = 0
NO_ADDRESS_AVAILABLE = 1
TOO_FEW_ROUTERS = 2
HAVE_CHILD_ID_REQUEST = 3
PARENT_PARTITION_CHANGE = 4
class TargetEid(object):
def __init__(self, eid):
self._eid = eid
@property
def eid(self):
return self._eid
def __eq__(self, other):
common.expect_the_same_class(self, other)
return self.eid == other.eid
def __repr__(self):
return "TargetEid(eid={})".format(hexlify(self.eid))
class TargetEidFactory(object):
def parse(self, data, message_info):
eid = bytearray(data.read(16))
return TargetEid(eid)
class MacExtendedAddress(object):
def __init__(self, mac_address):
self._mac_address = mac_address
@property
def mac_address(self):
return self._mac_address
def __eq__(self, other):
common.expect_the_same_class(self, other)
return self.mac_address == other.mac_address
def __repr__(self):
return "MacExtendedAddress(mac_address={})".format(hexlify(self.mac_address))
class MacExtendedAddressFactory(object):
def parse(self, data, message_info):
mac_address = bytearray(data.read(8))
return MacExtendedAddress(mac_address)
class Rloc16(object):
def __init__(self, rloc16):
self._rloc16 = rloc16
@property
def rloc16(self):
return self._rloc16
def __eq__(self, other):
common.expect_the_same_class(self, other)
return self.rloc16 == other.rloc16
def __repr__(self):
return "Rloc16(rloc16={})".format(hex(self.rloc16))
class Rloc16Factory(object):
def parse(self, data, message_info):
rloc16 = struct.unpack(">H", data.read(2))[0]
return Rloc16(rloc16)
class MlEid(object):
def __init__(self, ml_eid):
self._ml_eid = ml_eid
@property
def ml_eid(self):
return self._ml_eid
def __eq__(self, other):
common.expect_the_same_class(self, other)
return self.ml_eid == other.ml_eid
def __repr__(self):
return "MlEid(ml_eid={})".format(hexlify(self.ml_eid))
class MlEidFactory(object):
def parse(self, data, message_info):
ml_eid = bytearray(data.read(8))
return MlEid(ml_eid)
class Status(object):
def __init__(self, status):
self._status = status
@property
def status(self):
return self._status
def __eq__(self, other):
common.expect_the_same_class(self, other)
return self.status == other.status
def __repr__(self):
return "Status(status={})".format(self.status)
class StatusFactory(object):
def parse(self, data, message_info):
status = StatusValues(ord(data.read(1)))
return Status(status)
class TimeSinceLastTransaction(object):
def __init__(self, seconds):
self._seconds = seconds
@property
def seconds(self):
return self._seconds
def __eq__(self, other):
common.expect_the_same_class(self, other)
return self.seconds == other.seconds
def __repr__(self):
return "TimeSinceLastTransaction(seconds={})".format(self.seconds)
class TimeSinceLastTransactionFactory(object):
def parse(self, data, message_info):
seconds = struct.unpack(">L", data.read(4))[0]
return TimeSinceLastTransaction(seconds)
class RouterMask(object):
def __init__(self, id_sequence, router_id_mask):
self._id_sequence = id_sequence
self._router_id_mask = router_id_mask
@property
def id_sequence(self):
return self._id_sequence
@property
def router_id_mask(self):
return self._router_id_mask
def __eq__(self, other):
common.expect_the_same_class(self, other)
return self.id_sequence == other.id_sequence and self.router_id_mask == other.router_id_mask
def __repr__(self):
return "RouterMask(id_sequence={}, router_id_mask={})".format(self.id_sequence, hex(self.router_id_mask))
class RouterMaskFactory(object):
def parse(self, data, message_info):
id_sequence = ord(data.read(1))
router_id_mask = struct.unpack(">Q", data.read(8))[0]
return RouterMask(id_sequence, router_id_mask)
class NdOption(object):
def __init__(self, options):
self._options = options
@property
def options(self):
return self._options
def __eq__(self, other):
common.expect_the_same_class(self, other)
return self.options == other.options
def __repr__(self):
return "NdOption(options=[{}])".format(", ".join([str(opt) for opt in self.options]))
class NdOptionFactory(object):
def parse(self, data, message_info):
options = [opt for opt in bytearray(data.read())]
return NdOption(options)
class NdData(object):
# TODO: Not implemented yet
pass
class NdDataFactory(object):
# TODO: Not implemented yet
def parse(self, data, message_info):
raise NotImplementedError("TODO: Not implemented yet")
class ThreadNetworkData(object):
def __init__(self, tlvs):
self._tlvs = tlvs
@property
def tlvs(self):
return self._tlvs
def __eq__(self, other):
common.expect_the_same_class(self, other)
return self.tlvs == other.tlvs
def __repr__(self):
return "ThreadNetworkData(tlvs=[{}])".format(", ".join([str(tlv) for tlv in self.tlvs]))
class ThreadNetworkDataFactory(object):
def __init__(self, network_data_tlvs_factory):
self._network_data_tlvs_factory = network_data_tlvs_factory
def parse(self, data, message_info):
tlvs = self._network_data_tlvs_factory.parse(data, message_info)
return ThreadNetworkData(tlvs)
class NetworkLayerTlvsFactory(object):
def __init__(self, tlvs_factories):
self._tlvs_factories = tlvs_factories
def parse(self, data, message_info):
tlvs = []
while data.tell() < len(data.getvalue()):
_type = ord(data.read(1))
length = ord(data.read(1))
factory = self._tlvs_factories[_type]
tlv = factory.parse(io.BytesIO(data.read(length)), message_info)
tlvs.append(tlv)
return tlvs
+1 -1
View File
@@ -49,7 +49,7 @@ class Sniffer:
logger = logging.getLogger("sniffer.Sniffer")
POLL_TIMEOUT = 0.11
POLL_TIMEOUT = 0.10
RECV_BUFFER_SIZE = 4096
+524
View File
@@ -0,0 +1,524 @@
#!/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 string
import unittest
import coap
import config
def any_delta():
return random.getrandbits(4)
def any_coap_option_type():
return random.getrandbits(4)
def any_value():
return random.getrandbits(8)
def any_4bits_value_different_than_13_and_14():
value = None
while value is None:
value = random.getrandbits(4)
if value == 13 or value == 14:
value = None
return value
def any_4bits_value_lower_or_equal_than_12():
value = None
while value is None:
value = random.getrandbits(4)
if value > 12:
value = None
return value
def any_bytearray(length):
return bytearray([random.getrandbits(8) for _ in range(length)])
def any_version():
return random.getrandbits(2)
def any_type():
return random.getrandbits(2)
def any_code():
return random.getrandbits(8)
def any_message_id():
return random.getrandbits(16)
def any_token():
length = random.randint(0, 8)
return bytearray([random.getrandbits(8) for _ in range(length)])
def any_options():
return []
def any_payload(length=None):
length = length if length is not None else random.randint(0, 64)
return bytearray([random.getrandbits(8) for _ in range(length)])
def any_uri_path():
return "/" + random.choice(string.ascii_lowercase)
class TestCoapMessageOptionHeader(unittest.TestCase):
def test_should_return_passed_on_value_when_read_extended_value_is_called_with_value_different_than_13_and_14(self):
# GIVEN
value = any_4bits_value_different_than_13_and_14()
# WHEN
actual_value = coap.CoapOptionHeader._read_extended_value(None, value)
# THEN
self.assertEqual(value, actual_value)
def test_should_return_value_stored_in_first_byte_plus_13_when_read_extended_value_is_called_with_value_equal_13(self):
# GIVEN
value = 13
extended_value = any_value()
data = io.BytesIO(bytearray([extended_value]))
# WHEN
actual_value = coap.CoapOptionHeader._read_extended_value(data, value)
# THEN
self.assertEqual(extended_value + 13, actual_value)
def test_should_return_value_stored_in_first_byte_plus_269_when_read_extended_value_is_called_with_value_equal_14(self):
# GIVEN
value = 14
extended_value = any_value()
data = io.BytesIO(bytearray([any_value(), extended_value]))
# WHEN
actual_value = coap.CoapOptionHeader._read_extended_value(data, value)
# THEN
self.assertEqual(extended_value + 269, actual_value)
def test_should_create_CoapOptionHeader_when_from_bytes_classmethod_is_called(self):
# GIVEN
delta = any_4bits_value_different_than_13_and_14()
length = any_4bits_value_different_than_13_and_14()
data = bytearray([delta << 4 | length])
# WHEN
option_header = coap.CoapOptionHeader.from_bytes(io.BytesIO(data))
# THEN
self.assertEqual(delta, option_header.delta)
self.assertEqual(length, option_header.length)
def test_should_return_True_when_is_payload_marker_property_called_with_delta_and_length_equal_15(self):
# GIVEN
delta = 15
length = 15
data = bytearray([delta << 4 | length])
# WHEN
option_header = coap.CoapOptionHeader.from_bytes(io.BytesIO(data))
# THEN
self.assertTrue(option_header.is_payload_marker)
class TestCoapOption(unittest.TestCase):
def test_should_return_type_value_when_type_property_is_called(self):
# GIVEN
_type = any_coap_option_type()
coap_opt = coap.CoapOption(_type, any_value())
# WHEN
actual_type = coap_opt.type
# THEN
self.assertEqual(_type, actual_type)
def test_should_return_value_value_when_value_property_is_called(self):
# GIVEN
value = any_value()
coap_opt = coap.CoapOption(any_coap_option_type(), value)
# WHEN
actual_value = coap_opt.value
# THEN
self.assertEqual(value, actual_value)
class TestCoapOptionsFactory(unittest.TestCase):
def test_should_create_list_of_CoapOption_from_bytearray_when_parse_method_is_called(self):
# GIVEN
delta = any_4bits_value_lower_or_equal_than_12()
length = any_4bits_value_lower_or_equal_than_12()
value = any_bytearray(length)
data = bytearray([delta << 4 | length]) + value
factory = coap.CoapOptionsFactory()
# WHEN
coap_options = factory.parse(io.BytesIO(data), None)
# THEN
self.assertEqual(1, len(coap_options))
self.assertEqual(delta, coap_options[0].type)
self.assertEqual(value, coap_options[0].value)
class TestCoapCode(unittest.TestCase):
def test_should_return_code_value_when_code_property_is_called(self):
# GIVEN
code = any_code()
code_obj = coap.CoapCode(code)
# WHEN
actual_code = code_obj.code
# THEN
self.assertEqual(code, actual_code)
def test_should_return_class_value_when_class_property_is_called(self):
# GIVEN
code = any_code()
code_obj = coap.CoapCode(code)
# WHEN
actual_class = code_obj._class
# THEN
self.assertEqual((code >> 5) & 0x7, actual_class)
def test_should_return_detail_value_when_detail_property_is_called(self):
# GIVEN
code = any_code()
code_obj = coap.CoapCode(code)
# WHEN
actual_detail = code_obj.detail
# THEN
self.assertEqual(code & 0x1f, actual_detail)
def test_should_return_dotted_value_when_dotted_property_is_called(self):
# GIVEN
code = any_code()
code_obj = coap.CoapCode(code)
# WHEN
actual_dotted = code_obj.dotted
# THEN
_class, detail = actual_dotted.split(".")
self.assertEqual(code, (int(_class) << 5) | int(detail))
def test_should_create_CoapCode_when_from_class_and_detail_classmethod_is_called(self):
# GIVEN
code = any_code()
_class = (code >> 5) & 0x7
detail = code & 0x1f
# WHEN
actual_coap_obj = coap.CoapCode.from_class_and_detail(_class, detail)
# THEN
self.assertEqual(code, actual_coap_obj.code)
def test_should_create_CoapCode_when_from_dotted_string_classmethod_is_called(self):
# GIVEN
code = any_code()
code_obj = coap.CoapCode(code)
# WHEN
actual_coap_obj = coap.CoapCode.from_dotted(code_obj.dotted)
# THEN
self.assertEqual(code, actual_coap_obj.code)
class TestCoapMessage(unittest.TestCase):
def test_should_return_version_value_when_version_property_is_called(self):
# GIVEN
version = any_version()
coap_message = coap.CoapMessage(version, any_type(), any_code(), any_message_id(),
any_token(), any_options(), any_payload())
# WHEN
actual_version = coap_message.version
# THEN
self.assertEqual(version, actual_version)
def test_should_return_type_value_when_type_property_is_called(self):
# GIVEN
_type = any_type()
coap_message = coap.CoapMessage(any_version(), _type, any_code(), any_message_id(),
any_token(), any_options(), any_payload())
# WHEN
actual_type = coap_message.type
# THEN
self.assertEqual(_type, actual_type)
def test_should_return_code_value_when_code_property_is_called(self):
# GIVEN
code = any_code()
coap_message = coap.CoapMessage(any_version(), any_type(), code, any_message_id(),
any_token(), any_options(), any_payload())
# WHEN
actual_code = coap_message.code
# THEN
self.assertEqual(code, actual_code)
def test_should_return_message_id_value_when_message_id_property_is_called(self):
# GIVEN
message_id = any_message_id()
coap_message = coap.CoapMessage(any_version(), any_type(), any_code(), message_id,
any_token(), any_options(), any_payload())
# WHEN
actual_message_id = coap_message.message_id
# THEN
self.assertEqual(message_id, actual_message_id)
def test_should_return_token_value_when_token_property_is_called(self):
# GIVEN
token = any_token()
coap_message = coap.CoapMessage(any_version(), any_type(), any_code(), any_message_id(),
token, any_options(), any_payload())
# WHEN
actual_token = coap_message.token
# THEN
self.assertEqual(token, actual_token)
def test_should_return_tkl_value_when_tkl_property_is_called(self):
# GIVEN
token = any_token()
coap_message = coap.CoapMessage(any_version(), any_type(), any_code(), any_message_id(),
token, any_options(), any_payload())
# WHEN
actual_tkl = coap_message.tkl
# THEN
self.assertEqual(len(token), actual_tkl)
def test_should_return_options_value_when_options_property_is_called(self):
# GIVEN
options = any_options()
coap_message = coap.CoapMessage(any_version(), any_type(), any_code(), any_message_id(),
any_token(), options, any_payload())
# WHEN
actual_options = coap_message.options
# THEN
self.assertEqual(options, actual_options)
def test_should_return_payload_value_when_payload_property_is_called(self):
# GIVEN
payload = any_payload()
coap_message = coap.CoapMessage(any_version(), any_type(), any_code(), any_message_id(),
any_token(), any_options(), payload)
# WHEN
actual_payload = coap_message.payload
# THEN
self.assertEqual(payload, actual_payload)
def test_should_return_uri_path_value_when_uri_path_property_is_called(self):
# GIVEN
uri_path = any_uri_path()
coap_message = coap.CoapMessage(any_version(), any_type(), any_code(), any_message_id(),
any_token(), any_options(), any_payload(), uri_path)
# WHEN
actual_uri_path = coap_message.uri_path
# THEN
self.assertEqual(uri_path, actual_uri_path)
class TestCoapMessageIdToUriPathBinder(unittest.TestCase):
def test_should_add_uri_path_to_binds_when_add_uri_path_for_method_is_called(self):
# GIVEN
message_id = any_message_id()
token = any_token()
uri_path = any_uri_path()
binder = coap.CoapMessageIdToUriPathBinder()
# WHEN
binder.add_uri_path_for(message_id, token, uri_path)
# THEN
self.assertEqual(uri_path, binder.get_uri_path_for(message_id, token))
def test_should_raise_KeyError_when_get_uri_path_for_is_called_but_it_is_not_present_in_database(self):
# GIVEN
message_id = any_message_id()
token = any_token()
uri_path = any_uri_path()
binder = coap.CoapMessageIdToUriPathBinder()
# THEN
self.assertRaises(RuntimeError, binder.get_uri_path_for, message_id, token)
class TestCoapMessageFactory(unittest.TestCase):
def _create_dummy_payload_factory(self):
class DummyPayloadFactory:
def parse(self, data, message_info):
return data.read()
return DummyPayloadFactory()
def _create_coap_message_factory(self):
return coap.CoapMessageFactory(
options_factory=coap.CoapOptionsFactory(),
uri_path_based_payload_factories={
"/a/as": self._create_dummy_payload_factory()
},
message_id_to_uri_path_binder=coap.CoapMessageIdToUriPathBinder())
def test_should_create_CoapMessage_from_solicit_request_data_when_parse_method_is_called(self):
# GIVEN
data = bytearray([0x42, 0x02, 0x00, 0xbd, 0x65, 0xee, 0xb1, 0x61,
0x02, 0x61, 0x73, 0xff, 0x01, 0x08, 0x16, 0x6e,
0x0a, 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x01,
0x02])
factory = self._create_coap_message_factory()
# WHEN
coap_message = factory.parse(io.BytesIO(data), None)
# THEN
self.assertEqual(1, coap_message.version)
self.assertEqual(0, coap_message.type)
self.assertEqual(2, coap_message.tkl)
self.assertEqual(2, coap_message.code)
self.assertEqual(189, coap_message.message_id)
self.assertEqual(bytearray([0x65, 0xee]), coap_message.token)
self.assertEqual("a", coap_message.options[0].value.decode("utf-8"))
self.assertEqual("as", coap_message.options[1].value.decode("utf-8"))
self.assertEqual("/a/as", coap_message.uri_path)
self.assertEqual(bytearray([0x01, 0x08, 0x16, 0x6e, 0x0a, 0x00, 0x00, 0x00,
0x00, 0x02, 0x04, 0x01, 0x02]), coap_message.payload)
def test_should_create_CoapMessage_from_solicit_response_data_when_parse_method_is_called(self):
# GIVEN
data = bytearray([0x62, 0x44, 0x00, 0xbd, 0x65, 0xee, 0xff, 0x04,
0x01, 0x00, 0x02, 0x02, 0x00, 0x00, 0x07, 0x09,
0x76, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00])
mid_binder = coap.CoapMessageIdToUriPathBinder()
mid_binder.add_uri_path_for(189, bytearray([0x65, 0xee]), "/a/as")
factory = coap.CoapMessageFactory(
options_factory=coap.CoapOptionsFactory(),
uri_path_based_payload_factories={
"/a/as": self._create_dummy_payload_factory()
},
message_id_to_uri_path_binder=mid_binder)
# WHEN
coap_message = factory.parse(io.BytesIO(data), None)
# THEN
self.assertEqual(1, coap_message.version)
self.assertEqual(2, coap_message.type)
self.assertEqual(2, coap_message.tkl)
self.assertEqual("2.04", coap_message.code)
self.assertEqual(189, coap_message.message_id)
self.assertEqual(bytearray([0x65, 0xee]), coap_message.token)
self.assertEqual(None, coap_message.uri_path)
self.assertEqual(bytearray([0x04, 0x01, 0x00, 0x02, 0x02, 0x00, 0x00, 0x07,
0x09, 0x76, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00]), coap_message.payload)
if __name__ == "__main__":
unittest.main()
+3 -3
View File
@@ -115,7 +115,7 @@ class TestMacAddress(unittest.TestCase):
mac_address = common.MacAddress.from_eui64(eui64)
# THEN
self.assertEqual(common.MacAddress.LONG, mac_address.type)
self.assertEqual(common.MacAddressType.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):
@@ -126,7 +126,7 @@ class TestMacAddress(unittest.TestCase):
mac_address = common.MacAddress.from_rloc16(int(rloc16))
# THEN
self.assertEqual(common.MacAddress.SHORT, mac_address.type)
self.assertEqual(common.MacAddressType.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):
@@ -137,7 +137,7 @@ class TestMacAddress(unittest.TestCase):
mac_address = common.MacAddress.from_rloc16(rloc16)
# THEN
self.assertEqual(common.MacAddress.SHORT, mac_address.type)
self.assertEqual(common.MacAddressType.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):
View File
+117 -13
View File
@@ -38,8 +38,8 @@ from ipaddress import ip_address
from ipv6 import ICMPv6Header, UDPHeader, IPv6Header, IPv6PacketFactory, UDPDatagram, \
UDPDatagramFactory, ICMPv6Factory, HopByHopFactory, MPLOptionFactory, ICMPv6, HopByHopOptionHeader, HopByHopOption, \
HopByHop, MPLOption, HopByHopFactory, IPv6Packet, ICMPv6EchoBody, UDPBytesPayload, ICMPv6EchoBodyFactory, \
UpperLayerProtocol, UDPHeaderFactory, HopByHopOptionsFactory, \
UDPBytesPayloadFactory, ICMPv6DestinationUnreachable
UpperLayerProtocol, UDPHeaderFactory, HopByHopOptionsFactory, ICMPv6DestinationUnreachableFactory, \
UDPBytesPayloadFactory, ICMPv6DestinationUnreachable, UdpBasedOnSrcDstPortsPayloadFactory
import common
@@ -420,6 +420,13 @@ class TestUDPHeader(unittest.TestCase):
# THEN
self.assertEqual(8, udp_header_length)
def test_should_return_17_when_type_property_is_called(self):
# GIVEN
udp_header = UDPHeader(any_port(), any_port(), any_payload_length(), any_checksum())
# THEN
self.assertEqual(17, udp_header.type)
class TestICMPv6Header(unittest.TestCase):
@@ -609,6 +616,7 @@ class TestIPv6PacketFactory(unittest.TestCase):
# WHEN
ipv6_packet = ipv6_factory.parse(io.BytesIO(ipv6_packet_bytes), any_message_info())
ipv6_packet._validate_checksum()
# THEN
self.assertEqual('fd00:1234:4555::ff:fe00:1800', ipv6_packet.ipv6_header.source_address.compressed)
@@ -993,6 +1001,60 @@ class TestMPLOptionFactory(unittest.TestCase):
self.assertEqual(mpl_opt.seed_id, seed_id)
class TestUdpBasedOnSrcDstPortsPayloadFactory(unittest.TestCase):
def test_should_create_payload_from_data_when_src_port_factory_is_defined_and_parse_method_is_called(self):
# GIVEN
data = any_data()
message_info = common.MessageInfo()
message_info.src_port = any_port()
message_info.dst_port = any_port()
factory = UdpBasedOnSrcDstPortsPayloadFactory(
src_dst_port_based_payload_factories={
message_info.src_port: UDPBytesPayloadFactory()
})
# WHEN
actual_data = factory.parse(io.BytesIO(data), message_info)
# THEN
self.assertEqual(data, actual_data.data)
def test_should_create_payload_from_data_when_dst_port_factory_is_defined_and_parse_method_is_called(self):
# GIVEN
data = any_data()
message_info = common.MessageInfo()
message_info.src_port = any_port()
message_info.dst_port = any_port()
factory = UdpBasedOnSrcDstPortsPayloadFactory(
src_dst_port_based_payload_factories={
message_info.dst_port: UDPBytesPayloadFactory()
})
# WHEN
actual_data = factory.parse(io.BytesIO(data), message_info)
# THEN
self.assertEqual(data, actual_data.data)
def test_should_raise_RuntimeError_when_parse_method_is_called_but_required_factory_is_not_defined(self):
# GIVEN
data = any_data()
message_info = common.MessageInfo()
message_info.src_port = any_port()
message_info.dst_port = any_port()
factory = UdpBasedOnSrcDstPortsPayloadFactory(src_dst_port_based_payload_factories={})
# THEN
self.assertRaises(RuntimeError, factory.parse, io.BytesIO(data), message_info)
class TestUDPDatagramFactory(unittest.TestCase):
def test_should_produce_UDPDatagram_from_bytes_when_to_bytes_method_is_called_with_data(self):
@@ -1009,7 +1071,7 @@ class TestUDPDatagramFactory(unittest.TestCase):
(payload_length >> 8), (payload_length & 0xFF),
(checksum >> 8), (checksum & 0xFF)]) + payload
factory = UDPDatagramFactory(UDPHeaderFactory(), {dst_port: UDPBytesPayloadFactory()})
factory = UDPDatagramFactory(UDPHeaderFactory(), UDPBytesPayloadFactory())
# WHEN
udp_dgram = factory.parse(io.BytesIO(data), any_message_info())
@@ -1021,6 +1083,31 @@ class TestUDPDatagramFactory(unittest.TestCase):
self.assertEqual(udp_dgram.header.checksum, checksum)
self.assertEqual(udp_dgram.payload.data, payload)
def test_should_set_src_and_dst_port_in_message_info_when_parse_method_is_called(self):
# GIVEN
message_info = any_message_info()
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(), UDPBytesPayloadFactory())
# WHEN
udp_dgram = factory.parse(io.BytesIO(data), message_info)
# THEN
self.assertEqual(src_port, message_info.src_port)
self.assertEqual(dst_port, message_info.dst_port)
class TestICMPv6Factory(unittest.TestCase):
@@ -1208,22 +1295,39 @@ class TestICMPv6DestinationUnreachable(unittest.TestCase):
io.BytesIO(bytearray(struct.pack(">I", unused)) + data))
class TestICMPv6DestinationUnreachableFactory(unittest.TestCase):
def test_should_create_ICMPv6DestinationUnreachable_when_parse_method_is_called(self):
# GIVEN
icmp_data = any_data()
factory = ICMPv6DestinationUnreachableFactory()
data = bytearray([0x00, 0x00, 0x00, 0x00]) + icmp_data
# WHEN
icmpv6_dest_unreachable=factory.parse(io.BytesIO(data), any_message_info())
# THEN
self.assertEqual(icmp_data, icmpv6_dest_unreachable.data)
class TestUDPHeaderFactory(unittest.TestCase):
def test_should_create_UDPHeader_when_to_bytes_method_is_called(self):
# GIVEN
factory = UDPHeaderFactory()
factory=UDPHeaderFactory()
src_port = any_port()
dst_port = any_port()
payload_length = any_payload_length()
checksum = any_checksum()
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) + \
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())
udp_header=factory.parse(io.BytesIO(data), any_message_info())
# THEN
self.assertEqual(src_port, udp_header.src_port)
@@ -1242,12 +1346,12 @@ class TestHopByHopOptionsFactory(unittest.TestCase):
def parse(self, data, message_info):
return data.read()
factory = HopByHopOptionsFactory(options_factories={2: DummyOptionFactory()})
factory=HopByHopOptionsFactory(options_factories = {2: DummyOptionFactory()})
data = bytearray([0x02, 0x03, 0x11, 0x22, 0x33, 0x01, 0x00])
data=bytearray([0x02, 0x03, 0x11, 0x22, 0x33, 0x01, 0x00])
# WHEN
actual_options = factory.parse(io.BytesIO(data), any_message_info())
actual_options=factory.parse(io.BytesIO(data), any_message_info())
# THEN
self.assertEqual(1, len(actual_options))
+3 -7
View File
@@ -39,10 +39,6 @@ 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),
@@ -52,7 +48,7 @@ def create_default_lowpan_parser(context_manager):
ulpf={
17: ipv6.UDPDatagramFactory(
udp_header_factory=ipv6.UDPHeaderFactory(),
dst_port_factories=dst_port_factories),
udp_payload_factory=ipv6.UDPBytesPayloadFactory()),
58: ipv6.ICMPv6Factory(
body_factories=config.create_default_ipv6_icmp_body_factories()
)
@@ -2242,8 +2238,8 @@ class TestLowpanMeshHeaderFactory(unittest.TestCase):
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)
v = int(originator_address.type == common.MacAddressType.SHORT)
f = int(final_destination_address.type == common.MacAddressType.SHORT)
mesh_header_data = bytearray([(2 << 6) | (v << 5) | (f << 4) | hops_left]) + \
originator_address.mac_address + final_destination_address.mac_address
+2 -2
View File
@@ -30,7 +30,7 @@
import io
import unittest
from common import MacAddress
from common import MacAddress, MacAddressType
import mac802154
longaddrs = bytearray([0x61, 0xcc, 0x00, 0xce, 0xfa])
@@ -170,7 +170,7 @@ class TestMacParser(unittest.TestCase):
def test_should_decrypt_data_frame(self):
mac802154.DeviceDescriptors.add(0x2001, MacAddress(bytearray([0x16, 0x6e, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x07]),
MacAddress.LONG))
MacAddressType.LONG))
frame = mac802154.MacFrame()
frame.parse(io.BytesIO(bytearray([0x69, 0x98, 0x68, # FC, seq
View File
+29
View File
@@ -1,3 +1,32 @@
#!/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 math
import random
+504
View File
@@ -0,0 +1,504 @@
#!/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 network_layer
def any_eid():
return bytearray([random.getrandbits(8) for _ in range(16)])
def any_mac_extended_address():
return bytearray([random.getrandbits(8) for _ in range(8)])
def any_rloc16():
return random.getrandbits(16)
def any_ml_eid():
return bytearray([random.getrandbits(8) for _ in range(8)])
def any_status():
return random.getrandbits(1)
def any_seconds():
return random.getrandbits(32)
def any_id_sequence():
return random.getrandbits(8)
def any_router_id_mask():
return random.getrandbits(64)
def any_options(count=None):
count = count if count is not None else random.randint(0, 255)
return [random.getrandbits(8) for _ in range(count)]
def any_tlv_data(length=None):
_type = random.getrandbits(8)
length = length if length is not None else random.getrandbits(8)
value = bytearray([random.getrandbits(8) for _ in range(length)])
return bytearray([_type, length]) + value
def any_tlvs_data(count=None):
count = count if count is not None else random.randint(0, 16)
data = bytearray()
for _ in range(count):
data += any_tlv_data(random.randint(1, 15))
return data
class TestTargetEid(unittest.TestCase):
def test_should_return_eid_value_when_eid_property_is_called(self):
# GIVEN
eid = any_eid()
target_eid = network_layer.TargetEid(eid)
# WHEN
actual_eid = target_eid.eid
# THEN
self.assertEqual(eid, actual_eid)
def test_should_return_True_when_try_to_equal_two_the_same_type_objects_with_the_same_values(self):
# GIVEN
eid = any_eid()
target_eid = network_layer.TargetEid(eid)
# THEN
self.assertEqual(target_eid, network_layer.TargetEid(eid))
class TestTargetEidFactory(unittest.TestCase):
def test_should_create_TargetEid_from_bytearray_when_parse_method_is_called(self):
# GIVEN
eid = any_eid()
factory = network_layer.TargetEidFactory()
# WHEN
target_eid = factory.parse(io.BytesIO(eid), common.MessageInfo())
# THEN
self.assertTrue(isinstance(target_eid, network_layer.TargetEid))
self.assertEqual(eid, target_eid.eid)
class TestMacExtendedAddress(unittest.TestCase):
def test_should_return_mac_address_value_when_mac_address_property_is_called(self):
# GIVEN
mac_address = any_mac_extended_address()
mac_extended_address = network_layer.MacExtendedAddress(mac_address)
# WHEN
actual_mac_address = mac_extended_address.mac_address
# THEN
self.assertEqual(mac_address, actual_mac_address)
def test_should_return_True_when_try_to_equal_two_the_same_type_objects_with_the_same_values(self):
# GIVEN
mac_address = any_mac_extended_address()
mac_extended_address = network_layer.MacExtendedAddress(mac_address)
# THEN
self.assertEqual(mac_extended_address, network_layer.MacExtendedAddress(mac_address))
class TestMacExtendedAddressFactory(unittest.TestCase):
def test_should_create_MacExtendedAddress_from_bytearray_when_parse_method_is_called(self):
# GIVEN
mac_address = any_mac_extended_address()
factory = network_layer.MacExtendedAddressFactory()
# WHEN
mac_extended_address = factory.parse(io.BytesIO(mac_address), common.MessageInfo())
# THEN
self.assertTrue(isinstance(mac_extended_address, network_layer.MacExtendedAddress))
self.assertEqual(mac_address, mac_extended_address.mac_address)
class TestRloc16(unittest.TestCase):
def test_should_return_rloc16_value_when_rloc16_property_is_called(self):
# GIVEN
rloc16 = any_rloc16()
rloc16_obj = network_layer.Rloc16(rloc16)
# WHEN
actual_rloc16 = rloc16_obj.rloc16
# THEN
self.assertEqual(rloc16, actual_rloc16)
def test_should_return_True_when_try_to_equal_two_the_same_type_objects_with_the_same_values(self):
# GIVEN
rloc16 = any_rloc16()
rloc16_obj = network_layer.Rloc16(rloc16)
# THEN
self.assertEqual(rloc16_obj, network_layer.Rloc16(rloc16))
class TestRloc16Factory(unittest.TestCase):
def test_should_create_Rloc16_from_bytearray_when_parse_method_is_called(self):
# GIVEN
rloc16 = any_rloc16()
factory = network_layer.Rloc16Factory()
data = bytearray(struct.pack(">H", rloc16))
# WHEN
rloc16_obj = factory.parse(io.BytesIO(data), common.MessageInfo())
# THEN
self.assertTrue(isinstance(rloc16_obj, network_layer.Rloc16))
self.assertEqual(rloc16, rloc16_obj.rloc16)
class TestMlEid(unittest.TestCase):
def test_should_return_ml_eid_value_when_ml_eid_property_is_called(self):
# GIVEN
ml_eid = any_ml_eid()
ml_eid_obj = network_layer.MlEid(ml_eid)
# WHEN
actual_ml_eid = ml_eid_obj.ml_eid
# THEN
self.assertEqual(ml_eid, actual_ml_eid)
def test_should_return_True_when_try_to_equal_two_the_same_type_objects_with_the_same_values(self):
# GIVEN
ml_eid = any_ml_eid()
ml_eid_obj = network_layer.MlEid(ml_eid)
# THEN
self.assertEqual(ml_eid_obj, network_layer.MlEid(ml_eid))
class TestMlEidFactory(unittest.TestCase):
def test_should_create_MlEid_from_bytearray_when_parse_method_is_called(self):
# GIVEN
ml_eid = any_ml_eid()
factory = network_layer.MlEidFactory()
# WHEN
ml_eid_obj = factory.parse(io.BytesIO(ml_eid), common.MessageInfo())
# THEN
self.assertTrue(isinstance(ml_eid_obj, network_layer.MlEid))
self.assertEqual(ml_eid, ml_eid_obj.ml_eid)
class TestStatus(unittest.TestCase):
def test_should_return_status_value_when_status_property_is_called(self):
# GIVEN
status = any_status()
status_obj = network_layer.Status(status)
# WHEN
actual_status = status_obj.status
# THEN
self.assertEqual(status, actual_status)
def test_should_return_True_when_try_to_equal_two_the_same_type_objects_with_the_same_values(self):
# GIVEN
status = any_status()
status_obj = network_layer.Status(status)
# THEN
self.assertEqual(status_obj, network_layer.Status(status))
class TestStatusFactory(unittest.TestCase):
def test_should_create_Status_from_bytearray_when_parse_method_is_called(self):
# GIVEN
status = any_status()
factory = network_layer.StatusFactory()
data = bytearray([status])
# WHEN
status_obj = factory.parse(io.BytesIO(data), common.MessageInfo())
# THEN
self.assertTrue(isinstance(status_obj, network_layer.Status))
self.assertEqual(status, status_obj.status)
class TestTimeSinceLastTransaction(unittest.TestCase):
def test_should_return_seconds_value_when_seconds_property_is_called(self):
# GIVEN
seconds = any_seconds()
time_since_last_transaction = network_layer.TimeSinceLastTransaction(seconds)
# WHEN
actual_seconds = time_since_last_transaction.seconds
# THEN
self.assertEqual(seconds, actual_seconds)
def test_should_return_True_when_try_to_equal_two_the_same_type_objects_with_the_same_values(self):
# GIVEN
seconds = any_seconds()
time_since_last_transaction = network_layer.TimeSinceLastTransaction(seconds)
# THEN
self.assertEqual(time_since_last_transaction, network_layer.TimeSinceLastTransaction(seconds))
class TestTimeSinceLastTransactionFactory(unittest.TestCase):
def test_should_create_TimeSinceLastTransaction_from_bytearray_when_parse_method_is_called(self):
# GIVEN
seconds = any_seconds()
factory = network_layer.TimeSinceLastTransactionFactory()
data = bytearray(struct.pack(">L", seconds))
# WHEN
time_since_last_transaction = factory.parse(io.BytesIO(data), common.MessageInfo())
# THEN
self.assertTrue(isinstance(time_since_last_transaction, network_layer.TimeSinceLastTransaction))
self.assertEqual(seconds, time_since_last_transaction.seconds)
class TestRouterMask(unittest.TestCase):
def test_should_return_id_sequence_value_when_id_sequence_property_is_called(self):
# GIVEN
id_sequence = any_id_sequence()
router_mask = network_layer.RouterMask(id_sequence, any_router_id_mask())
# WHEN
actual_id_sequence = router_mask.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()
router_mask = network_layer.RouterMask(any_id_sequence(), router_id_mask)
# WHEN
actual_router_id_mask = router_mask.router_id_mask
# THEN
self.assertEqual(router_id_mask, actual_router_id_mask)
def test_should_return_True_when_try_to_equal_two_the_same_type_objects_with_the_same_values(self):
# GIVEN
id_sequence = any_id_sequence()
router_id_mask = any_router_id_mask()
router_mask = network_layer.RouterMask(id_sequence, router_id_mask)
# THEN
self.assertEqual(router_mask, network_layer.RouterMask(id_sequence, router_id_mask))
class TestRouterMaskFactory(unittest.TestCase):
def test_should_create_RouterMask_from_bytearray_when_parse_method_is_called(self):
# GIVEN
id_sequence = any_id_sequence()
router_id_mask = any_router_id_mask()
factory = network_layer.RouterMaskFactory()
data = bytearray([id_sequence]) + struct.pack(">Q", router_id_mask)
# WHEN
router_mask = factory.parse(io.BytesIO(data), common.MessageInfo())
# THEN
self.assertTrue(isinstance(router_mask, network_layer.RouterMask))
self.assertEqual(id_sequence, router_mask.id_sequence)
self.assertEqual(router_id_mask, router_mask.router_id_mask)
class TestNdOption(unittest.TestCase):
def test_should_return_options_value_when_options_property_is_called(self):
# GIVEN
options = any_options()
nd_option = network_layer.NdOption(options)
# WHEN
actual_options = nd_option.options
# THEN
self.assertEqual(options, actual_options)
def test_should_return_True_when_try_to_equal_two_the_same_type_objects_with_the_same_values(self):
# GIVEN
options = any_options()
nd_option = network_layer.NdOption(options)
# THEN
self.assertEqual(nd_option, network_layer.NdOption(options))
class TestNdOptionFactory(unittest.TestCase):
def test_should_create_NdOption_from_bytearray_when_parse_method_is_called(self):
# GIVEN
options = any_options()
factory = network_layer.NdOptionFactory()
data = bytearray(options)
# WHEN
nd_option = factory.parse(io.BytesIO(data), common.MessageInfo())
# THEN
self.assertTrue(isinstance(nd_option, network_layer.NdOption))
self.assertEqual(options, nd_option.options)
class TestThreadNetworkData(unittest.TestCase):
def test_should_return_options_value_when_options_property_is_called(self):
# GIVEN
tlvs = any_tlvs_data()
thread_network_data = network_layer.ThreadNetworkData(tlvs)
# WHEN
actual_tlvs = thread_network_data.tlvs
# THEN
self.assertEqual(tlvs, actual_tlvs)
def test_should_return_True_when_try_to_equal_two_the_same_type_objects_with_the_same_values(self):
# GIVEN
tlvs = any_tlvs_data()
thread_network_data = network_layer.ThreadNetworkData(tlvs)
# THEN
self.assertEqual(thread_network_data, network_layer.ThreadNetworkData(tlvs))
class TestThreadNetworkDataFactory(unittest.TestCase):
def test_should_create_ThreadNetworkData_from_bytearray_when_parse_method_is_called(self):
# GIVEN
tlvs = any_tlvs_data()
class DummyNetworkDataTlvsFactory:
def parse(self, data, message_info):
return bytearray(data.read())
factory = network_layer.ThreadNetworkDataFactory(DummyNetworkDataTlvsFactory())
# WHEN
thread_network_data = factory.parse(io.BytesIO(tlvs), common.MessageInfo())
# THEN
self.assertTrue(isinstance(thread_network_data, network_layer.ThreadNetworkData))
self.assertEqual(tlvs, thread_network_data.tlvs)
class TestNetworkLayerTlvsFactory(unittest.TestCase):
def test_should_create_tlv_when_parse_method_is_called(self):
# GIVEN
class DummyNetworkDataTlvsFactory:
def parse(self, data, message_info):
return bytearray(data.read())
tlv = any_tlv_data()
factory = network_layer.NetworkLayerTlvsFactory({tlv[0]: DummyNetworkDataTlvsFactory()})
# WHEN
actual_tlvs = factory.parse(io.BytesIO(tlv), None)
# THEN
self.assertEqual(tlv[2:], actual_tlvs[0])
if __name__ == "__main__":
unittest.main()