331 lines
8.7 KiB
Python
331 lines
8.7 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
#
|
||
|
|
# Copyright (c) 2020, The OpenThread Authors.
|
||
|
|
# All rights reserved.
|
||
|
|
#
|
||
|
|
# Redistribution and use in source and binary forms, with or without
|
||
|
|
# modification, are permitted provided that the following conditions are met:
|
||
|
|
# 1. Redistributions of source code must retain the above copyright
|
||
|
|
# notice, this list of conditions and the following disclaimer.
|
||
|
|
# 2. Redistributions in binary form must reproduce the above copyright
|
||
|
|
# notice, this list of conditions and the following disclaimer in the
|
||
|
|
# documentation and/or other materials provided with the distribution.
|
||
|
|
# 3. Neither the name of the copyright holder nor the
|
||
|
|
# names of its contributors may be used to endorse or promote products
|
||
|
|
# derived from this software without specific prior written permission.
|
||
|
|
#
|
||
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||
|
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||
|
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||
|
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||
|
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||
|
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||
|
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||
|
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||
|
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
||
|
|
#
|
||
|
|
|
||
|
|
import struct
|
||
|
|
|
||
|
|
from enum import IntEnum
|
||
|
|
from typing import List
|
||
|
|
|
||
|
|
import common
|
||
|
|
import ipaddress
|
||
|
|
import mle
|
||
|
|
|
||
|
|
|
||
|
|
class TlvType(IntEnum):
|
||
|
|
EXT_ADDRESS = 0
|
||
|
|
ADDRESS16 = 1
|
||
|
|
MODE = 2
|
||
|
|
POLLING_PERIOD = 3
|
||
|
|
CONNECTIVITY = 4
|
||
|
|
ROUTE64 = 5
|
||
|
|
LEADER_DATA = 6
|
||
|
|
NETWORK_DATA = 7
|
||
|
|
IPV6_ADDRESS_LIST = 8
|
||
|
|
MAC_COUNTERS = 9
|
||
|
|
BATTERY_LEVEL = 14
|
||
|
|
SUPPLY_VOLTAGE = 15
|
||
|
|
CHILD_TABLE = 16
|
||
|
|
CHANNEL_PAGES = 17
|
||
|
|
TYPE_LIST = 18
|
||
|
|
MAX_CHILD_TIMEOUT = 19
|
||
|
|
|
||
|
|
|
||
|
|
class Ipv6AddressList:
|
||
|
|
|
||
|
|
def __init__(self, addresses: List[ipaddress.IPv6Address]):
|
||
|
|
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):
|
||
|
|
return f'Ipv6AddressList({self.addresses})'
|
||
|
|
|
||
|
|
|
||
|
|
class Ipv6AddressListFactory:
|
||
|
|
|
||
|
|
def parse(self, data, message_info):
|
||
|
|
addresses = []
|
||
|
|
while data.tell() < message_info.length:
|
||
|
|
addresses.append(ipaddress.IPv6Address(data.read(16)))
|
||
|
|
return Ipv6AddressList(addresses)
|
||
|
|
|
||
|
|
|
||
|
|
class MacCounters:
|
||
|
|
|
||
|
|
def __init__(self, counters: List[int]):
|
||
|
|
self._counters = counters
|
||
|
|
|
||
|
|
@property
|
||
|
|
def if_in_unknown_protos(self):
|
||
|
|
return self._counters[0]
|
||
|
|
|
||
|
|
@property
|
||
|
|
def if_in_errors(self):
|
||
|
|
return self._counters[1]
|
||
|
|
|
||
|
|
@property
|
||
|
|
def if_out_errors(self):
|
||
|
|
return self._counters[2]
|
||
|
|
|
||
|
|
@property
|
||
|
|
def if_in_ucast_pkts(self):
|
||
|
|
return self._counters[3]
|
||
|
|
|
||
|
|
@property
|
||
|
|
def if_in_broadcast_pkts(self):
|
||
|
|
return self._counters[4]
|
||
|
|
|
||
|
|
@property
|
||
|
|
def if_in_discards(self):
|
||
|
|
return self._counters[5]
|
||
|
|
|
||
|
|
@property
|
||
|
|
def if_out_ucast_pkts(self):
|
||
|
|
return self._counters[6]
|
||
|
|
|
||
|
|
@property
|
||
|
|
def if_out_broadcast_pkts(self):
|
||
|
|
return self._counters[7]
|
||
|
|
|
||
|
|
@property
|
||
|
|
def if_out_discards(self):
|
||
|
|
return self._counters[8]
|
||
|
|
|
||
|
|
@property
|
||
|
|
def counters(self):
|
||
|
|
return self._counters
|
||
|
|
|
||
|
|
def __eq__(self, other):
|
||
|
|
common.expect_the_same_class(self, other)
|
||
|
|
|
||
|
|
return self.counters == other.counters
|
||
|
|
|
||
|
|
def __repr__(self):
|
||
|
|
return ('MacCounters(' + f'if_in_unknown_protos={self.if_in_unknown_protos}, ' +
|
||
|
|
f'if_in_errors={self.if_in_errors}, ' + f'if_out_errors={self.if_out_errors}, ' +
|
||
|
|
f'if_in_ucast_pkts={self.if_in_ucast_pkts}, ' + f'if_in_broadcast_pkts={self.if_in_broadcast_pkts}, ' +
|
||
|
|
f'if_in_discards={self.if_in_discards}, ' + f'if_out_ucast_pkts={self.if_out_ucast_pkts}, ' +
|
||
|
|
f'if_out_broadcast_pkts={self.if_out_broadcast_pkts}, ' + f'if_out_discards={self.if_out_discards})')
|
||
|
|
|
||
|
|
|
||
|
|
class MacCountersFactory:
|
||
|
|
|
||
|
|
def parse(self, data, message_info):
|
||
|
|
return MacCounters(struct.unpack('>9I', data.read(4 * 9)))
|
||
|
|
|
||
|
|
|
||
|
|
class BatteryLevel:
|
||
|
|
|
||
|
|
def __init__(self, battery_level: int):
|
||
|
|
self._battery_level = battery_level
|
||
|
|
|
||
|
|
@property
|
||
|
|
def battery_level(self):
|
||
|
|
return self._battery_level
|
||
|
|
|
||
|
|
def __eq__(self, other):
|
||
|
|
common.expect_the_same_class(self, other)
|
||
|
|
|
||
|
|
return self.battery_level == other.battery_level
|
||
|
|
|
||
|
|
def __repr__(self):
|
||
|
|
return f'BatteryLevel(battery_level={self.battery_level})'
|
||
|
|
|
||
|
|
|
||
|
|
class BatteryLevelFactory:
|
||
|
|
|
||
|
|
def parse(self, data, message_info):
|
||
|
|
return BatteryLevel(struct.unpack('>B', data.read(1))[0])
|
||
|
|
|
||
|
|
|
||
|
|
class SupplyVoltage:
|
||
|
|
|
||
|
|
def __init__(self, supply_voltage: int):
|
||
|
|
self._supply_voltage = supply_voltage
|
||
|
|
|
||
|
|
@property
|
||
|
|
def supply_voltage(self):
|
||
|
|
return self._supply_voltage
|
||
|
|
|
||
|
|
def __eq__(self, other):
|
||
|
|
common.expect_the_same_class(self, other)
|
||
|
|
|
||
|
|
return self.supply_voltage == other.supply_voltage
|
||
|
|
|
||
|
|
def __repr__(self):
|
||
|
|
return f'SupplyVoltage(supply_voltage={self.supply_voltage})'
|
||
|
|
|
||
|
|
|
||
|
|
class SupplyVoltageFactory:
|
||
|
|
|
||
|
|
def parse(self, data, message_info):
|
||
|
|
return SupplyVoltage(struct.unpack('>H', data.read(2))[0])
|
||
|
|
|
||
|
|
|
||
|
|
class ChildTableEntry:
|
||
|
|
|
||
|
|
def __init__(self, timeout: int, child_id: int, mode: mle.Mode):
|
||
|
|
self._timeout = timeout
|
||
|
|
self._child_id = child_id
|
||
|
|
self._mode = mode
|
||
|
|
|
||
|
|
@property
|
||
|
|
def timeout(self):
|
||
|
|
return self._timeout
|
||
|
|
|
||
|
|
@property
|
||
|
|
def child_id(self):
|
||
|
|
return self._child_id
|
||
|
|
|
||
|
|
@property
|
||
|
|
def mode(self):
|
||
|
|
return self._mode
|
||
|
|
|
||
|
|
def __eq__(self, other):
|
||
|
|
common.expect_the_same_class(self, other)
|
||
|
|
|
||
|
|
return (self.timeout == other.timeout and self.child_id == other.child_id and self.mode == other.mode)
|
||
|
|
|
||
|
|
def __repr__(self):
|
||
|
|
return f'ChildTableEntry(timeout={self.timeout}, child_id={self.child_id}, mode={self.mode})'
|
||
|
|
|
||
|
|
|
||
|
|
class ChildTable:
|
||
|
|
|
||
|
|
def __init__(self, children: List[ChildTableEntry]):
|
||
|
|
self._children = sorted(children, key=lambda child: child.child_id)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def children(self):
|
||
|
|
return self._children
|
||
|
|
|
||
|
|
def __eq__(self, other):
|
||
|
|
common.expect_the_same_class(self, other)
|
||
|
|
|
||
|
|
return self.children == other.children
|
||
|
|
|
||
|
|
def __repr__(self):
|
||
|
|
return f'ChildTable({self.children})'
|
||
|
|
|
||
|
|
|
||
|
|
class ChildTableFactory:
|
||
|
|
|
||
|
|
def parse(self, data, message_info):
|
||
|
|
children = []
|
||
|
|
while message_info.length > 0:
|
||
|
|
timeout_and_id = struct.unpack('>H', data.read(2))[0]
|
||
|
|
message_info.length -= 2
|
||
|
|
|
||
|
|
timeout = (timeout_and_id & 0xf800) >> 11
|
||
|
|
child_id = timeout_and_id & 0x1fff
|
||
|
|
|
||
|
|
mode = mle.ModeFactory().parse(data, message_info)
|
||
|
|
message_info.length -= 1
|
||
|
|
|
||
|
|
children.append(ChildTableEntry(timeout, child_id, mode))
|
||
|
|
return ChildTable(children)
|
||
|
|
|
||
|
|
|
||
|
|
class ChannelPages:
|
||
|
|
|
||
|
|
def __init__(self, channel_pages: bytes):
|
||
|
|
self._channel_pages = channel_pages
|
||
|
|
|
||
|
|
@property
|
||
|
|
def channel_pages(self):
|
||
|
|
return self._channel_pages
|
||
|
|
|
||
|
|
def __eq__(self, other):
|
||
|
|
common.expect_the_same_class(self, other)
|
||
|
|
|
||
|
|
return self.channel_pages == other.channel_pages
|
||
|
|
|
||
|
|
def __repr__(self):
|
||
|
|
return f'ChannelPages(channel_pages={self.channel_pages})'
|
||
|
|
|
||
|
|
|
||
|
|
class ChannelPagesFactory:
|
||
|
|
|
||
|
|
def parse(self, data, message_info):
|
||
|
|
return ChannelPages(data.getvalue())
|
||
|
|
|
||
|
|
|
||
|
|
class TypeList:
|
||
|
|
|
||
|
|
def __init__(self, tlv_types: List[int]):
|
||
|
|
self._tlv_types = tlv_types
|
||
|
|
|
||
|
|
@property
|
||
|
|
def tlv_types(self):
|
||
|
|
return self._tlv_types
|
||
|
|
|
||
|
|
def __eq__(self, other):
|
||
|
|
common.expect_the_same_class(self, other)
|
||
|
|
|
||
|
|
return self.tlv_types == other.tlv_types
|
||
|
|
|
||
|
|
def __repr__(self):
|
||
|
|
return f'TypeList(tlv_types={self.tlv_types})'
|
||
|
|
|
||
|
|
|
||
|
|
class TypeListFactory:
|
||
|
|
|
||
|
|
def parse(self, data, message_info):
|
||
|
|
return TypeList([ord(t) for t in data.getvalue()])
|
||
|
|
|
||
|
|
|
||
|
|
class MaxChildTimeout:
|
||
|
|
|
||
|
|
def __init__(self, max_child_timeout: int):
|
||
|
|
self._max_child_timeout = max_child_timeout
|
||
|
|
|
||
|
|
@property
|
||
|
|
def max_child_timeout(self):
|
||
|
|
return self._max_child_timeout
|
||
|
|
|
||
|
|
def __eq__(self, other):
|
||
|
|
common.expect_the_same_class(self, other)
|
||
|
|
|
||
|
|
return self.max_child_timeout == other.max_child_timeout
|
||
|
|
|
||
|
|
def __repr__(self):
|
||
|
|
return f'MaxChildTimeout(max_child_timeout={self.max_child_timeout})'
|
||
|
|
|
||
|
|
|
||
|
|
class MaxChildTimeoutFactory:
|
||
|
|
|
||
|
|
def parse(self, data, message_info):
|
||
|
|
return MaxChildTimeout(struct.unpack('>I', data.read(4))[0])
|