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").
1305 lines
33 KiB
Python
Executable File
1305 lines
33 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 io
|
|
import logging
|
|
import struct
|
|
|
|
from binascii import hexlify
|
|
|
|
import common
|
|
|
|
from enum import IntEnum
|
|
from tlvs_parsing import UnknownTlvFactory
|
|
|
|
|
|
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
|
|
LINK_METRICS_MANAGEMENT_REQUEST = 18
|
|
LINK_METRICS_MANAGEMENT_RESPONSE = 19
|
|
LINK_PROBE = 20
|
|
TIME_SYNC = 99
|
|
|
|
|
|
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
|
|
CSL_CHANNEL = 80
|
|
CSL_SYNCHRONIZED_TIMEOUT = 85
|
|
LINK_METRICS_QUERY = 87
|
|
LINK_METRICS_MANAGEMENT = 88
|
|
LINK_METRICS_REPORT = 89
|
|
LINK_PROBE = 90
|
|
TIME_REQUEST = 252
|
|
TIME_PARAMETER = 253
|
|
|
|
|
|
class LinkMetricsSubTlvType(IntEnum):
|
|
LINK_METRICS_REPORT = 0
|
|
LINK_METRICS_QUERY_ID = 1
|
|
LINK_METRICS_QUERY_OPTIONS = 2
|
|
FORWARD_PROBING_REGISTRATION = 3
|
|
LINK_METRICS_STATUS = 5
|
|
ENHANCED_ACK_LINK_METRICS_CONFIGURATION = 7
|
|
|
|
|
|
class SourceAddress(object):
|
|
|
|
def __init__(self, address):
|
|
self._address = address
|
|
|
|
@property
|
|
def address(self):
|
|
return self._address
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return self.address == other.address
|
|
|
|
def __repr__(self):
|
|
return "SourceAddress(address={})".format(hex(self._address))
|
|
|
|
|
|
class SourceAddressFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
address = struct.unpack(">H", data.read(2))[0]
|
|
return SourceAddress(address)
|
|
|
|
|
|
class Mode(object):
|
|
|
|
def __init__(self, receiver, secure, device_type, network_data):
|
|
self._receiver = receiver
|
|
self._secure = secure
|
|
self._device_type = device_type
|
|
self._network_data = network_data
|
|
|
|
@property
|
|
def receiver(self):
|
|
return self._receiver
|
|
|
|
@property
|
|
def secure(self):
|
|
return self._secure
|
|
|
|
@property
|
|
def device_type(self):
|
|
return self._device_type
|
|
|
|
@property
|
|
def network_data(self):
|
|
return self._network_data
|
|
|
|
def __eq__(self, other):
|
|
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)
|
|
|
|
def __repr__(self):
|
|
return "Mode(receiver={}, secure={}, device_type={}, network_data={})".format(
|
|
self.receiver, self.secure, self.device_type, self.network_data)
|
|
|
|
|
|
class ModeFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
mode = ord(data.read(1))
|
|
receiver = (mode >> 3) & 0x01
|
|
secure = (mode >> 2) & 0x01
|
|
device_type = (mode >> 1) & 0x01
|
|
network_data = (mode >> 0) & 0x01
|
|
return Mode(receiver, secure, device_type, network_data)
|
|
|
|
|
|
class Timeout(object):
|
|
|
|
def __init__(self, timeout):
|
|
self._timeout = timeout
|
|
|
|
@property
|
|
def timeout(self):
|
|
return self._timeout
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return self.timeout == other.timeout
|
|
|
|
def __repr__(self):
|
|
return "Timeout(timeout={})".format(self.timeout)
|
|
|
|
|
|
class TimeoutFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
timeout = struct.unpack(">I", data.read(4))[0]
|
|
return Timeout(timeout)
|
|
|
|
|
|
class Challenge(object):
|
|
|
|
def __init__(self, challenge):
|
|
self._challenge = challenge
|
|
|
|
@property
|
|
def challenge(self):
|
|
return self._challenge
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return self.challenge == other.challenge
|
|
|
|
def __repr__(self):
|
|
return "Challenge(challenge={})".format(hexlify(self.challenge))
|
|
|
|
|
|
class ChallengeFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
challenge = data.read()
|
|
return Challenge(challenge)
|
|
|
|
|
|
class Response(object):
|
|
|
|
def __init__(self, response):
|
|
self._response = response
|
|
|
|
@property
|
|
def response(self):
|
|
return self._response
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return self.response == other.response
|
|
|
|
def __repr__(self):
|
|
return "Response(response={})".format(hexlify(self.response))
|
|
|
|
|
|
class ResponseFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
response = data.read()
|
|
return Response(response)
|
|
|
|
|
|
class LinkLayerFrameCounter(object):
|
|
|
|
def __init__(self, frame_counter):
|
|
self._frame_counter = frame_counter
|
|
|
|
@property
|
|
def frame_counter(self):
|
|
return self._frame_counter
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return self.frame_counter == other.frame_counter
|
|
|
|
def __repr__(self):
|
|
return "LinkLayerFrameCounter(frame_counter={})".format(self.frame_counter)
|
|
|
|
|
|
class LinkLayerFrameCounterFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
frame_counter = struct.unpack(">I", data.read(4))[0]
|
|
return LinkLayerFrameCounter(frame_counter)
|
|
|
|
|
|
class MleFrameCounter(object):
|
|
|
|
def __init__(self, frame_counter):
|
|
self._frame_counter = frame_counter
|
|
|
|
@property
|
|
def frame_counter(self):
|
|
return self._frame_counter
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return self.frame_counter == other.frame_counter
|
|
|
|
def __repr__(self):
|
|
return "MleFrameCounter(frame_counter={})".format(self.frame_counter)
|
|
|
|
|
|
class MleFrameCounterFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
frame_counter = struct.unpack(">I", data.read(4))[0]
|
|
return MleFrameCounter(frame_counter)
|
|
|
|
|
|
class LinkQualityAndRouteData(object):
|
|
|
|
def __init__(self, output, _input, route):
|
|
self._output = output
|
|
self._input = _input
|
|
self._route = route
|
|
|
|
@property
|
|
def output(self):
|
|
return self._output
|
|
|
|
@property
|
|
def input(self):
|
|
return self._input
|
|
|
|
@property
|
|
def route(self):
|
|
return self._route
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return (self.output == other.output and self.input == other.input and self.route == other.route)
|
|
|
|
def __repr__(self):
|
|
return "LinkQualityAndRouteData(ouput={}, input={}, route={})".format(self.output, self.input, self.route)
|
|
|
|
|
|
class LinkQualityAndRouteDataFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
lqrd = ord(data.read(1))
|
|
output = (lqrd >> 6) & 0x3
|
|
_input = (lqrd >> 4) & 0x3
|
|
route = lqrd & 0x0F
|
|
return LinkQualityAndRouteData(output, _input, route)
|
|
|
|
|
|
class Route64(object):
|
|
|
|
def __init__(self, id_sequence, router_id_mask, link_quality_and_route_data):
|
|
self._id_sequence = id_sequence
|
|
self._router_id_mask = router_id_mask
|
|
self._link_quality_and_route_data = link_quality_and_route_data
|
|
|
|
@property
|
|
def id_sequence(self):
|
|
return self._id_sequence
|
|
|
|
@property
|
|
def router_id_mask(self):
|
|
return self._router_id_mask
|
|
|
|
@property
|
|
def link_quality_and_route_data(self):
|
|
return self._link_quality_and_route_data
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return (self.id_sequence == other.id_sequence and self.router_id_mask == other.router_id_mask and
|
|
self.link_quality_and_route_data == other.link_quality_and_route_data)
|
|
|
|
def __repr__(self):
|
|
lqrd_str = ", ".join(["{}".format(lqrd) for lqrd in self.link_quality_and_route_data])
|
|
return "Route64(id_sequence={}, router_id_mask={}, link_quality_and_route_data=[{}])".format(
|
|
self.id_sequence, hex(self.router_id_mask), lqrd_str)
|
|
|
|
|
|
class Route64Factory:
|
|
|
|
def __init__(self, link_quality_and_route_data_factory):
|
|
self._lqrd_factory = link_quality_and_route_data_factory
|
|
|
|
def parse(self, data, message_info):
|
|
id_sequence = ord(data.read(1))
|
|
router_id_mask = struct.unpack(">Q", data.read(8))[0]
|
|
|
|
link_quality_and_route_data = []
|
|
|
|
while data.tell() < len(data.getvalue()):
|
|
link_quality_and_route_data.append(self._lqrd_factory.parse(data, message_info))
|
|
|
|
return Route64(id_sequence, router_id_mask, link_quality_and_route_data)
|
|
|
|
|
|
class Address16(object):
|
|
|
|
def __init__(self, address):
|
|
self._address = address
|
|
|
|
@property
|
|
def address(self):
|
|
return self._address
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return self.address == other.address
|
|
|
|
def __repr__(self):
|
|
return "Address16(address={})".format(hex(self.address))
|
|
|
|
|
|
class Address16Factory:
|
|
|
|
def parse(self, data, message_info):
|
|
address = struct.unpack(">H", data.read(2))[0]
|
|
return Address16(address)
|
|
|
|
|
|
class LeaderData(object):
|
|
|
|
def __init__(
|
|
self,
|
|
partition_id,
|
|
weighting,
|
|
data_version,
|
|
stable_data_version,
|
|
leader_router_id,
|
|
):
|
|
self._partition_id = partition_id
|
|
self._weighting = weighting
|
|
self._data_version = data_version
|
|
self._stable_data_version = stable_data_version
|
|
self._leader_router_id = leader_router_id
|
|
|
|
@property
|
|
def partition_id(self):
|
|
return self._partition_id
|
|
|
|
@property
|
|
def weighting(self):
|
|
return self._weighting
|
|
|
|
@property
|
|
def data_version(self):
|
|
return self._data_version
|
|
|
|
@property
|
|
def stable_data_version(self):
|
|
return self._stable_data_version
|
|
|
|
@property
|
|
def leader_router_id(self):
|
|
return self._leader_router_id
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return (self.partition_id == other.partition_id and self.weighting == other.weighting and
|
|
self.data_version == other.data_version and self.stable_data_version == other.stable_data_version and
|
|
self.leader_router_id == other.leader_router_id)
|
|
|
|
def __repr__(self):
|
|
return 'LeaderData(partition_id={}, weighting={}, data_version={}, stable_data_version={},leader_router_id={}'.format(
|
|
self.partition_id,
|
|
self.weighting,
|
|
self.data_version,
|
|
self.stable_data_version,
|
|
self.leader_router_id,
|
|
)
|
|
|
|
|
|
class LeaderDataFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
partition_id = struct.unpack(">I", data.read(4))[0]
|
|
weighting = ord(data.read(1))
|
|
data_version = ord(data.read(1))
|
|
stable_data_version = ord(data.read(1))
|
|
leader_router_id = ord(data.read(1))
|
|
return LeaderData(
|
|
partition_id,
|
|
weighting,
|
|
data_version,
|
|
stable_data_version,
|
|
leader_router_id,
|
|
)
|
|
|
|
|
|
class NetworkData(object):
|
|
|
|
def __init__(self, tlvs):
|
|
self._tlvs = tlvs
|
|
|
|
@property
|
|
def tlvs(self):
|
|
return self._tlvs
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return self.tlvs == other.tlvs
|
|
|
|
def __repr__(self):
|
|
tlvs_str = ", ".join(["{}".format(tlv) for tlv in self.tlvs])
|
|
return "NetworkData(tlvs=[{}])".format(tlvs_str)
|
|
|
|
|
|
class NetworkDataFactory:
|
|
|
|
def __init__(self, network_data_tlvs_factory):
|
|
self._tlvs_factory = network_data_tlvs_factory
|
|
|
|
def parse(self, data, message_info):
|
|
tlvs = self._tlvs_factory.parse(data, message_info)
|
|
return NetworkData(tlvs)
|
|
|
|
|
|
class TlvRequest(object):
|
|
|
|
def __init__(self, tlvs):
|
|
self._tlvs = tlvs
|
|
|
|
@property
|
|
def tlvs(self):
|
|
return self._tlvs
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return self.tlvs == other.tlvs
|
|
|
|
def __repr__(self):
|
|
tlvs_str = ", ".join(["{}".format(tlv) for tlv in self.tlvs])
|
|
return "TlvRequest(tlvs=[{}])".format(tlvs_str)
|
|
|
|
|
|
class TlvRequestFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
tlvs = [b for b in bytearray(data.read())]
|
|
return TlvRequest(tlvs)
|
|
|
|
|
|
class ScanMask(object):
|
|
|
|
def __init__(self, router, end_device):
|
|
self._router = router
|
|
self._end_device = end_device
|
|
|
|
@property
|
|
def router(self):
|
|
return self._router
|
|
|
|
@property
|
|
def end_device(self):
|
|
return self._end_device
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return (self.router == other.router and self.end_device == other.end_device)
|
|
|
|
def __repr__(self):
|
|
return "ScanMask(router={}, end_device={})".format(self.router, self.end_device)
|
|
|
|
|
|
class ScanMaskFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
scan_mask = ord(data.read(1))
|
|
router = (scan_mask >> 7) & 0x01
|
|
end_device = (scan_mask >> 6) & 0x01
|
|
return ScanMask(router, end_device)
|
|
|
|
|
|
class Connectivity(object):
|
|
|
|
def __init__(
|
|
self,
|
|
pp_byte,
|
|
link_quality_3,
|
|
link_quality_2,
|
|
link_quality_1,
|
|
leader_cost,
|
|
id_sequence,
|
|
active_routers,
|
|
sed_buffer_size=None,
|
|
sed_datagram_count=None,
|
|
):
|
|
self._pp_byte = pp_byte
|
|
self._link_quality_3 = link_quality_3
|
|
self._link_quality_2 = link_quality_2
|
|
self._link_quality_1 = link_quality_1
|
|
self._leader_cost = leader_cost
|
|
self._id_sequence = id_sequence
|
|
self._active_routers = active_routers
|
|
self._sed_buffer_size = sed_buffer_size
|
|
self._sed_datagram_count = sed_datagram_count
|
|
|
|
@property
|
|
def pp_byte(self):
|
|
return self._pp_byte
|
|
|
|
@property
|
|
def pp(self):
|
|
return common.map_pp(self._pp_byte)
|
|
|
|
@property
|
|
def link_quality_3(self):
|
|
return self._link_quality_3
|
|
|
|
@property
|
|
def link_quality_2(self):
|
|
return self._link_quality_2
|
|
|
|
@property
|
|
def link_quality_1(self):
|
|
return self._link_quality_1
|
|
|
|
@property
|
|
def leader_cost(self):
|
|
return self._leader_cost
|
|
|
|
@property
|
|
def id_sequence(self):
|
|
return self._id_sequence
|
|
|
|
@property
|
|
def active_routers(self):
|
|
return self._active_routers
|
|
|
|
@property
|
|
def sed_buffer_size(self):
|
|
return self._sed_buffer_size
|
|
|
|
@property
|
|
def sed_datagram_count(self):
|
|
return self._sed_datagram_count
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return (self.pp == other.pp and self.link_quality_3 == other.link_quality_3 and
|
|
self.link_quality_2 == other.link_quality_2 and self.link_quality_1 == other.link_quality_1 and
|
|
self.leader_cost == other.leader_cost and self.id_sequence == other.id_sequence and
|
|
self.active_routers == other.active_routers and self.sed_buffer_size == other.sed_buffer_size and
|
|
self.sed_datagram_count == other.sed_datagram_count)
|
|
|
|
def __repr__(self):
|
|
return r"Connectivity(pp={}, \
|
|
link_quality_3={}, \
|
|
link_quality_2={}, \
|
|
link_quality_1={}, \
|
|
leader_cost={}, \
|
|
id_sequence={}, \
|
|
active_routers={}, \
|
|
sed_buffer_size={}, \
|
|
sed_datagram_count={})".format(
|
|
self.pp,
|
|
self.link_quality_3,
|
|
self.link_quality_2,
|
|
self.link_quality_1,
|
|
self.leader_cost,
|
|
self.id_sequence,
|
|
self.active_routers,
|
|
self.sed_buffer_size,
|
|
self.sed_datagram_count,
|
|
)
|
|
|
|
|
|
class ConnectivityFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
pp_byte = ord(data.read(1))
|
|
link_quality_3 = ord(data.read(1))
|
|
link_quality_2 = ord(data.read(1))
|
|
link_quality_1 = ord(data.read(1))
|
|
leader_cost = ord(data.read(1))
|
|
id_sequence = ord(data.read(1))
|
|
active_routers = ord(data.read(1))
|
|
|
|
sed_data = io.BytesIO(data.read(3))
|
|
|
|
if len(sed_data.getvalue()) > 0:
|
|
sed_buffer_size = struct.unpack(">H", sed_data.read(2))[0]
|
|
sed_datagram_count = ord(sed_data.read(1))
|
|
else:
|
|
sed_buffer_size = None
|
|
sed_datagram_count = None
|
|
|
|
return Connectivity(
|
|
pp_byte,
|
|
link_quality_3,
|
|
link_quality_2,
|
|
link_quality_1,
|
|
leader_cost,
|
|
id_sequence,
|
|
active_routers,
|
|
sed_buffer_size,
|
|
sed_datagram_count,
|
|
)
|
|
|
|
|
|
class LinkMargin(object):
|
|
|
|
def __init__(self, link_margin):
|
|
self._link_margin = link_margin
|
|
|
|
@property
|
|
def link_margin(self):
|
|
return self._link_margin
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return self.link_margin == other.link_margin
|
|
|
|
def __repr__(self):
|
|
return "LinkMargin(link_margin={})".format(self.link_margin)
|
|
|
|
|
|
class LinkMarginFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
link_margin = ord(data.read(1))
|
|
return LinkMargin(link_margin)
|
|
|
|
|
|
class Status(object):
|
|
|
|
def __init__(self, status):
|
|
self._status = status
|
|
|
|
@property
|
|
def status(self):
|
|
return self._status
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return self.status == other.status
|
|
|
|
def __repr__(self):
|
|
return "Status(status={})".format(self.status)
|
|
|
|
|
|
class StatusFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
status = ord(data.read(1))
|
|
return Status(status)
|
|
|
|
|
|
class Version(object):
|
|
|
|
def __init__(self, version):
|
|
self._version = version
|
|
|
|
@property
|
|
def version(self):
|
|
return self._version
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return self.version == other.version
|
|
|
|
def __repr__(self):
|
|
return "Version(version={})".format(self.version)
|
|
|
|
|
|
class VersionFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
version = struct.unpack(">H", data.read(2))[0]
|
|
return Version(version)
|
|
|
|
|
|
class AddressFull(object):
|
|
|
|
def __init__(self, ipv6_address):
|
|
self._ipv6_address = ipv6_address
|
|
|
|
@property
|
|
def ipv6_address(self):
|
|
return self._ipv6_address
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return self.ipv6_address == other.ipv6_address
|
|
|
|
def __repr__(self):
|
|
return "AddressFull(ipv6_address={}')".format(hexlify(self.ipv6_address))
|
|
|
|
|
|
class AddressFullFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
data.read(1) # first byte is ignored
|
|
ipv6_address = data.read(16)
|
|
return AddressFull(ipv6_address)
|
|
|
|
|
|
class AddressCompressed(object):
|
|
|
|
def __init__(self, cid, iid):
|
|
self._cid = cid
|
|
self._iid = iid
|
|
|
|
@property
|
|
def cid(self):
|
|
return self._cid
|
|
|
|
@property
|
|
def iid(self):
|
|
return self._iid
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return self.cid == other.cid and self.iid == other.iid
|
|
|
|
def __repr__(self):
|
|
return "AddressCompressed(cid={}, iid={}')".format(self.cid, hexlify(self.iid))
|
|
|
|
|
|
class AddressCompressedFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
cid = ord(data.read(1)) & 0x0F
|
|
iid = bytearray(data.read(8))
|
|
return AddressCompressed(cid, iid)
|
|
|
|
|
|
class AddressRegistration(object):
|
|
|
|
def __init__(self, addresses):
|
|
self._addresses = addresses
|
|
|
|
@property
|
|
def addresses(self):
|
|
return self._addresses
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return self.addresses == other.addresses
|
|
|
|
def __repr__(self):
|
|
addresses_str = ", ".join(["{}".format(address) for address in self.addresses])
|
|
return "AddressRegistration(addresses=[{}])".format(addresses_str)
|
|
|
|
|
|
class AddressRegistrationFactory:
|
|
|
|
def __init__(self, addr_compressed_factory, addr_full_factory):
|
|
self._addr_compressed_factory = addr_compressed_factory
|
|
self._addr_full_factory = addr_full_factory
|
|
|
|
def parse(self, data, message_info):
|
|
addresses = []
|
|
|
|
while data.tell() < len(data.getvalue()):
|
|
compressed = (ord(data.read(1)) >> 7) & 0x01
|
|
data.seek(-1, io.SEEK_CUR)
|
|
|
|
if compressed:
|
|
addresses.append(self._addr_compressed_factory.parse(data, message_info))
|
|
else:
|
|
addresses.append(self._addr_full_factory.parse(data, message_info))
|
|
|
|
return AddressRegistration(addresses)
|
|
|
|
|
|
class Channel(object):
|
|
|
|
def __init__(self, channel_page, channel):
|
|
self._channel_page = channel_page
|
|
self._channel = channel
|
|
|
|
@property
|
|
def channel_page(self):
|
|
return self._channel_page
|
|
|
|
@property
|
|
def channel(self):
|
|
return self._channel
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return (self.channel_page == other.channel_page and self.channel == other.channel)
|
|
|
|
def __repr__(self):
|
|
return "Channel(channel_page={}, channel={})".format(self.channel_page, self.channel)
|
|
|
|
|
|
class ChannelFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
channel_page = ord(data.read(1))
|
|
channel = struct.unpack(">H", data.read(2))[0]
|
|
return Channel(channel_page, channel)
|
|
|
|
|
|
class PanId:
|
|
|
|
def __init__(self, pan_id):
|
|
self._pan_id = pan_id
|
|
|
|
@property
|
|
def pan_id(self):
|
|
return self._pan_id
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return self.pan_id == other.pan_id
|
|
|
|
def __repr__(self):
|
|
return "PanId(pan_id={})".format(self.pan_id)
|
|
|
|
|
|
class PanIdFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
pan_id = struct.unpack(">H", data.read(2))[0]
|
|
return PanId(pan_id)
|
|
|
|
|
|
class ActiveTimestamp(object):
|
|
|
|
def __init__(self, timestamp_seconds, timestamp_ticks, u):
|
|
self._timestamp_seconds = timestamp_seconds
|
|
self._timestamp_ticks = timestamp_ticks
|
|
self._u = u
|
|
|
|
@property
|
|
def timestamp_seconds(self):
|
|
return self._timestamp_seconds
|
|
|
|
@property
|
|
def timestamp_ticks(self):
|
|
return self._timestamp_ticks
|
|
|
|
@property
|
|
def u(self):
|
|
return self._u
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return (self.timestamp_seconds == other.timestamp_seconds and self.timestamp_ticks == other.timestamp_ticks and
|
|
self.u == other.u)
|
|
|
|
def __repr__(self):
|
|
return "ActiveTimestamp(timestamp_seconds={}, timestamp_ticks={}, u={})".format(
|
|
self.timestamp_seconds, self.timestamp_ticks, self.u)
|
|
|
|
|
|
class ActiveTimestampFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
seconds = bytearray([0x00, 0x00]) + bytearray(data.read(6))
|
|
ticks = struct.unpack(">H", data.read(2))[0]
|
|
|
|
timestamp_seconds = struct.unpack(">Q", bytes(seconds))[0]
|
|
timestamp_ticks = ticks >> 1
|
|
u = ticks & 0x01
|
|
return ActiveTimestamp(timestamp_seconds, timestamp_ticks, u)
|
|
|
|
|
|
class PendingTimestamp(object):
|
|
|
|
def __init__(self, timestamp_seconds, timestamp_ticks, u):
|
|
self._timestamp_seconds = timestamp_seconds
|
|
self._timestamp_ticks = timestamp_ticks
|
|
self._u = u
|
|
|
|
@property
|
|
def timestamp_seconds(self):
|
|
return self._timestamp_seconds
|
|
|
|
@property
|
|
def timestamp_ticks(self):
|
|
return self._timestamp_ticks
|
|
|
|
@property
|
|
def u(self):
|
|
return self._u
|
|
|
|
def __eq__(self, other):
|
|
common.expect_the_same_class(self, other)
|
|
|
|
return (self.timestamp_seconds == other.timestamp_seconds and self.timestamp_ticks == other.timestamp_ticks and
|
|
self.u == other.u)
|
|
|
|
def __repr__(self):
|
|
return "PendingTimestamp(timestamp_seconds={}, timestamp_ticks={}, u={})".format(
|
|
self.timestamp_seconds, self.timestamp_ticks, self.u)
|
|
|
|
|
|
class PendingTimestampFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
seconds = bytearray([0x00, 0x00]) + bytearray(data.read(6))
|
|
ticks = struct.unpack(">H", data.read(2))[0]
|
|
|
|
timestamp_seconds = struct.unpack(">Q", bytes(seconds))[0]
|
|
timestamp_ticks = ticks >> 1
|
|
u = ticks & 0x01
|
|
return PendingTimestamp(timestamp_seconds, timestamp_ticks, u)
|
|
|
|
|
|
class ActiveOperationalDataset:
|
|
# TODO: Not implemented yet
|
|
|
|
def __init__(self):
|
|
print("ActiveOperationalDataset is not implemented yet.")
|
|
|
|
|
|
class ActiveOperationalDatasetFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
return ActiveOperationalDataset()
|
|
|
|
|
|
class PendingOperationalDataset:
|
|
# TODO: Not implemented yet
|
|
|
|
def __init__(self):
|
|
print("PendingOperationalDataset is not implemented yet.")
|
|
|
|
|
|
class PendingOperationalDatasetFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
return PendingOperationalDataset()
|
|
|
|
|
|
class ThreadDiscovery(object):
|
|
|
|
def __init__(self, tlvs):
|
|
self._tlvs = tlvs
|
|
|
|
@property
|
|
def tlvs(self):
|
|
return self._tlvs
|
|
|
|
def __eq__(self, other):
|
|
return self.tlvs == other.tlvs
|
|
|
|
def __repr__(self):
|
|
return "ThreadDiscovery(tlvs={})".format(self.tlvs)
|
|
|
|
|
|
class ThreadDiscoveryFactory:
|
|
|
|
def __init__(self, thread_discovery_tlvs_factory):
|
|
self._tlvs_factory = thread_discovery_tlvs_factory
|
|
|
|
def parse(self, data, message_info):
|
|
tlvs = self._tlvs_factory.parse(data, message_info)
|
|
return ThreadDiscovery(tlvs)
|
|
|
|
|
|
class CslChannel:
|
|
# TODO: Not implemented yet
|
|
|
|
def __init__(self):
|
|
print("CslChannel is not implemented yet.")
|
|
|
|
|
|
class CslChannelFactory:
|
|
# TODO: Not implemented yet
|
|
|
|
def parse(self, data, message_info):
|
|
return CslChannel()
|
|
|
|
|
|
class CslSynchronizedTimeout:
|
|
# TODO: Not implemented yet
|
|
|
|
def __init__(self):
|
|
print("CslSynchronizedTimeout is not implemented yet.")
|
|
|
|
|
|
class CslSynchronizedTimeoutFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
return CslSynchronizedTimeout()
|
|
|
|
|
|
class TimeRequest:
|
|
# TODO: Not implemented yet
|
|
|
|
def __init__(self):
|
|
print("TimeRequest is not implemented yet.")
|
|
|
|
|
|
class TimeRequestFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
return TimeRequest()
|
|
|
|
|
|
class TimeParameter:
|
|
# TODO: Not implemented yet
|
|
|
|
def __init__(self):
|
|
print("TimeParameter is not implemented yet.")
|
|
|
|
|
|
class TimeParameterFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
return TimeParameter()
|
|
|
|
|
|
class LinkMetricsQuery:
|
|
# TODO: Not implemented yet
|
|
|
|
def __init__(self):
|
|
print("LinkMetricsQuery is not implemented yet.")
|
|
|
|
|
|
class LinkMetricsQueryFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
return LinkMetricsQuery()
|
|
|
|
|
|
class LinkMetricsManagement:
|
|
# TODO: Not implemented yet
|
|
|
|
def __init__(self):
|
|
print("LinkMetricsManagement is not implemented yet.")
|
|
|
|
|
|
class LinkMetricsManagementFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
return LinkMetricsManagement()
|
|
|
|
|
|
class LinkMetricsReport:
|
|
# TODO: Not implemented yet
|
|
|
|
def __init__(self):
|
|
print("LinkMetricsReport is not implemented yet.")
|
|
|
|
|
|
class LinkMetricsReportFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
return LinkMetricsReport()
|
|
|
|
|
|
class LinkProbe:
|
|
# TODO: Not implemented yet
|
|
|
|
def __init__(self):
|
|
print("LinkProbe is not implemented yet.")
|
|
|
|
|
|
class LinkProbeFactory:
|
|
|
|
def parse(self, data, message_info):
|
|
return LinkProbe()
|
|
|
|
|
|
class MleCommand(object):
|
|
|
|
def __init__(self, _type, tlvs):
|
|
self._type = _type
|
|
self._tlvs = tlvs
|
|
|
|
@property
|
|
def type(self):
|
|
return self._type
|
|
|
|
@property
|
|
def tlvs(self):
|
|
return self._tlvs
|
|
|
|
def __repr__(self):
|
|
tlvs_str = ", ".join(["{}".format(tlv) for tlv in self.tlvs])
|
|
return "MleCommand(type={}, tlvs=[{}])".format(self.type.name, tlvs_str)
|
|
|
|
|
|
class MleCommandFactory:
|
|
|
|
_MARKER_EXTENDED_LENGTH = 0xff
|
|
|
|
def __init__(self, tlvs_factories):
|
|
self._tlvs_factories = tlvs_factories
|
|
|
|
def _get_length(self, data):
|
|
length = ord(data.read(1))
|
|
|
|
if length == self._MARKER_EXTENDED_LENGTH:
|
|
length = struct.unpack(">H", data.read(2))[0]
|
|
|
|
return length
|
|
|
|
def _get_tlv_factory(self, _type):
|
|
try:
|
|
return self._tlvs_factories[_type]
|
|
except KeyError:
|
|
logging.error('Could not find TLV factory. Unsupported TLV type: {}'.format(_type))
|
|
return UnknownTlvFactory(_type)
|
|
|
|
def _parse_tlv(self, data, message_info):
|
|
_type = TlvType(ord(data.read(1)))
|
|
length = self._get_length(data)
|
|
value = data.read(length)
|
|
|
|
factory = self._get_tlv_factory(_type)
|
|
|
|
return factory.parse(io.BytesIO(value), message_info)
|
|
|
|
def parse(self, data, message_info):
|
|
cmd_type = CommandType(ord(data.read(1)))
|
|
tlvs = []
|
|
|
|
while data.tell() < len(data.getvalue()):
|
|
tlv = self._parse_tlv(data, message_info)
|
|
tlvs.append(tlv)
|
|
|
|
return MleCommand(cmd_type, tlvs)
|
|
|
|
|
|
class MleMessage(object):
|
|
|
|
def __init__(self, command):
|
|
self._command = command
|
|
|
|
@property
|
|
def command(self):
|
|
return self._command
|
|
|
|
def __repr__(self):
|
|
return "MleMessage(command={})".format(self.command)
|
|
|
|
|
|
class MleMessageSecured(MleMessage):
|
|
|
|
def __init__(self, aux_sec_hdr, command, mic):
|
|
super(MleMessageSecured, self).__init__(command)
|
|
self._aux_sec_hdr = aux_sec_hdr
|
|
self._mic = mic
|
|
|
|
@property
|
|
def aux_sec_hdr(self):
|
|
return self._aux_sec_hdr
|
|
|
|
@property
|
|
def mic(self):
|
|
return self._mic
|
|
|
|
def __repr__(self):
|
|
return "MleMessageSecured(aux_sec_hdr={}, command={}, mic=\"{}\")".format(self.aux_sec_hdr, self.command,
|
|
hexlify(self.mic))
|
|
|
|
|
|
class MleMessageFactory:
|
|
|
|
def __init__(self, aux_sec_hdr_factory, mle_command_factory, crypto_engine):
|
|
self._aux_sec_hdr_factory = aux_sec_hdr_factory
|
|
self._mle_command_factory = mle_command_factory
|
|
self._crypto_engine = crypto_engine
|
|
|
|
def _create_mle_secured_message(self, data, message_info):
|
|
aux_sec_hdr = self._aux_sec_hdr_factory.parse(data, message_info)
|
|
|
|
enc_data_length = len(data.getvalue())
|
|
|
|
enc_data = bytearray(data.read(enc_data_length - data.tell() - self._crypto_engine.mic_length))
|
|
mic = bytearray(data.read())
|
|
|
|
dec_data = self._crypto_engine.decrypt(enc_data, mic, message_info)
|
|
|
|
command = self._mle_command_factory.parse(io.BytesIO(dec_data), message_info)
|
|
|
|
return MleMessageSecured(aux_sec_hdr, command, mic)
|
|
|
|
def _create_mle_message(self, data, message_info):
|
|
command = self._mle_command_factory.parse(data, message_info)
|
|
|
|
return MleMessage(command)
|
|
|
|
def parse(self, data, message_info):
|
|
security_indicator = ord(data.read(1))
|
|
|
|
if security_indicator == 0:
|
|
return self._create_mle_secured_message(data, message_info)
|
|
|
|
elif security_indicator == 255:
|
|
return self._create_mle_message(data, message_info)
|
|
|
|
else:
|
|
raise RuntimeError(
|
|
"Could not create MLE message. Unknown security indicator value: {}".format(security_indicator))
|