mirror of
https://github.com/espressif/openthread.git
synced 2026-07-08 21:30:24 +00:00
71e00eb20f
This commits updates `thread-cert/Makefile.am` adding some existing tests that were not included in the Makefile when the tests were added. It also sorts the entries to be in alphabetical order. This commit also updates the access control mode for all script files to be executable by all (`chmod 755` or "-rwxr-xr-x").
431 lines
12 KiB
Python
Executable File
431 lines
12 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# 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,
|
|
)
|