mirror of
https://github.com/espressif/openthread.git
synced 2026-07-09 13:50:28 +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").
1135 lines
35 KiB
Python
Executable File
1135 lines
35 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 ipaddress
|
|
import struct
|
|
|
|
import common
|
|
import ipv6
|
|
|
|
|
|
class LowpanIPHC:
|
|
"""
|
|
Class representing a compressed IP header.
|
|
|
|
More details:
|
|
- URL: https://tools.ietf.org/html/rfc6282
|
|
- section: 3.1. LOWPAN_IPHC Encoding Format
|
|
"""
|
|
|
|
def __init__(self, tf, nh, hlim, cid, sac, sam, m, dac, dam):
|
|
self._tf = tf
|
|
self._nh = nh
|
|
self._hlim = hlim
|
|
self._cid = cid
|
|
self._sac = sac
|
|
self._sam = sam
|
|
self._m = m
|
|
self._dac = dac
|
|
self._dam = dam
|
|
|
|
@property
|
|
def tf(self):
|
|
return self._tf
|
|
|
|
@property
|
|
def nh(self):
|
|
return self._nh
|
|
|
|
@property
|
|
def hlim(self):
|
|
return self._hlim
|
|
|
|
@property
|
|
def cid(self):
|
|
return self._cid
|
|
|
|
@property
|
|
def sac(self):
|
|
return self._sac
|
|
|
|
@property
|
|
def sam(self):
|
|
return self._sam
|
|
|
|
@property
|
|
def m(self):
|
|
return self._m
|
|
|
|
@property
|
|
def dac(self):
|
|
return self._dac
|
|
|
|
@property
|
|
def dam(self):
|
|
return self._dam
|
|
|
|
@classmethod
|
|
def from_bytes(cls, data_bytes):
|
|
data_byte = data_bytes[0]
|
|
|
|
hdr = (data_byte >> 5) & 0x07
|
|
if hdr != 0x03:
|
|
raise RuntimeError("Not a 6LowPAN packet.")
|
|
|
|
tf = (data_byte >> 3) & 0x03
|
|
nh = (data_byte >> 2) & 0x01
|
|
hlim = data_byte & 0x03
|
|
|
|
data_byte = data_bytes[1]
|
|
|
|
cid = (data_byte >> 7) & 0x01
|
|
sac = (data_byte >> 6) & 0x01
|
|
sam = (data_byte >> 4) & 0x03
|
|
m = (data_byte >> 3) & 0x01
|
|
dac = (data_byte >> 2) & 0x01
|
|
dam = data_byte & 0x03
|
|
|
|
return cls(tf, nh, hlim, cid, sac, sam, m, dac, dam)
|
|
|
|
def __repr__(self):
|
|
return "LowpanIPHC(tf={}, nh={}, hlim={}, cid={}, sac={}, sam={}, m={}, dac={}, dam={})".format(
|
|
self.tf,
|
|
self.nh,
|
|
self.hlim,
|
|
self.cid,
|
|
self.sac,
|
|
self.sam,
|
|
self.m,
|
|
self.dac,
|
|
self.dam,
|
|
)
|
|
|
|
|
|
class LowpanNHC:
|
|
"""
|
|
Class representing a compressed extension header.
|
|
|
|
More details:
|
|
- URL: https://tools.ietf.org/html/rfc6282
|
|
- section: 4.1. LOWPAN_NHC Format
|
|
|
|
"""
|
|
|
|
NHC_EID_HOP_BY_HOP = 0
|
|
NHC_EID_ROUTING = 1
|
|
NHC_EID_FRAGMENT = 2
|
|
NHC_EID_DST_OPT = 3
|
|
NHC_EID_MOBILITY = 4
|
|
NHC_EID_IPV6_HDR = 7
|
|
|
|
def __init__(self, eid, nh):
|
|
self._eid = eid
|
|
self._nh = nh
|
|
|
|
@property
|
|
def eid(self):
|
|
return self._eid
|
|
|
|
@property
|
|
def nh(self):
|
|
return self._nh
|
|
|
|
@classmethod
|
|
def from_bytes(cls, data_bytes):
|
|
header_byte = data_bytes[0]
|
|
|
|
eid = (header_byte >> 1) & 0x07
|
|
nh = header_byte & 0x01
|
|
|
|
return cls(eid, nh)
|
|
|
|
def __repr__(self):
|
|
return "LowpanNHC(eid={}, nh={})".format(self.eid, self.nh)
|
|
|
|
|
|
class LowpanUDPHC:
|
|
"""
|
|
Class representing compressed UDP header.
|
|
|
|
More details:
|
|
- URL: https://tools.ietf.org/html/rfc6282
|
|
- section: 4.3.3. UDP LOWPAN_NHC Format
|
|
|
|
"""
|
|
|
|
def __init__(self, c, p):
|
|
self._c = c
|
|
self._p = p
|
|
|
|
@property
|
|
def c(self):
|
|
return self._c
|
|
|
|
@property
|
|
def p(self):
|
|
return self._p
|
|
|
|
@classmethod
|
|
def from_bytes(cls, data_bytes):
|
|
data_byte = data_bytes[0]
|
|
|
|
hdr = (data_byte >> 3) & 0x1F
|
|
if hdr != 0x1E:
|
|
raise RuntimeError("Not a 6LowPAN UDP header.")
|
|
|
|
c = (data_byte >> 2) & 0x01
|
|
p = data_byte & 0x03
|
|
|
|
return cls(c, p)
|
|
|
|
def __repr__(self):
|
|
return "LowpanUDPHC(c={}, p={})".format(self.c, self.p)
|
|
|
|
|
|
class LowpanHopByHopFactory:
|
|
""" Factory that produces HopByHop extension header. """
|
|
|
|
def __init__(self, hop_by_hop_options_factory):
|
|
self._hop_by_hop_options_factory = hop_by_hop_options_factory
|
|
|
|
def parse(self, data, next_header, message_info):
|
|
ext_header_length = ord(data.read(1))
|
|
|
|
ext_header_data = data.read(ext_header_length)
|
|
|
|
options = self._hop_by_hop_options_factory.parse(io.BytesIO(ext_header_data), message_info)
|
|
|
|
ext_header = ipv6.HopByHop(next_header, options)
|
|
|
|
message_info.payload_length += len(ext_header)
|
|
|
|
return ext_header
|
|
|
|
|
|
class LowpanExtensionHeadersFactory:
|
|
""" Factory that produces extension headers. """
|
|
|
|
NHC_NH_INLINE = 0
|
|
NHC_NH_COMPRESSED = 1
|
|
|
|
def __init__(self, ext_headers_factories):
|
|
self._ext_headers_factories = (ext_headers_factories if ext_headers_factories is not None else {})
|
|
|
|
def _decompress_nh(self, hc, data):
|
|
if hc.nh == self.NHC_NH_INLINE:
|
|
return ord(data.read(1))
|
|
|
|
elif hc.nh == self.NHC_NH_COMPRESSED:
|
|
return None
|
|
|
|
def _get_ext_headers_factory(self, eid):
|
|
try:
|
|
return self._ext_headers_factories[eid]
|
|
|
|
except BaseException:
|
|
raise RuntimeError("Could not find an extension header factory for the EID type: {}".format(eid))
|
|
|
|
def parse(self, data, message_info):
|
|
nhc = LowpanNHC.from_bytes(bytearray(data.read(1)))
|
|
|
|
next_header = self._decompress_nh(nhc, data)
|
|
|
|
factory = self._get_ext_headers_factory(nhc.eid)
|
|
|
|
return factory.parse(data, next_header, message_info)
|
|
|
|
|
|
class LowpanUdpHeaderFactory:
|
|
""" Factory producing UDP header. """
|
|
|
|
UDP_HC_C_INLINE = 0
|
|
UDP_HC_C_ELIDED = 1
|
|
|
|
UDP_HC_P_BOTH_FULL = 0
|
|
UDP_HC_P_DST_COMPR = 1
|
|
UDP_HC_P_SRC_COMPR = 2
|
|
UDP_HC_P_BOTH_COMPR = 3
|
|
|
|
def _decompress_udp_ports(self, udphc, data):
|
|
if udphc.p == self.UDP_HC_P_BOTH_FULL:
|
|
src_port = struct.unpack(">H", data.read(2))[0]
|
|
dst_port = struct.unpack(">H", data.read(2))[0]
|
|
|
|
elif udphc.p == self.UDP_HC_P_DST_COMPR:
|
|
src_port = struct.unpack(">H", data.read(2))[0]
|
|
dst_port = 0xf000 + ord(data.read(1))
|
|
|
|
elif udphc.p == self.UDP_HC_P_SRC_COMPR:
|
|
src_port = 0xf000 + ord(data.read(1))
|
|
dst_port = struct.unpack(">H", data.read(2))[0]
|
|
|
|
elif udphc.p == self.UDP_HC_P_BOTH_COMPR:
|
|
udp_ports_byte = ord(data.read(1))
|
|
src_port = 0xf0b0 + ((udp_ports_byte >> 4) & 0x0F)
|
|
dst_port = 0xf0b0 + (udp_ports_byte & 0x0F)
|
|
|
|
return src_port, dst_port
|
|
|
|
def _decompress_udp_checksum(self, udphc, data):
|
|
if udphc.c == self.UDP_HC_C_INLINE:
|
|
checksum = struct.unpack(">H", data.read(2))[0]
|
|
|
|
if udphc.c == self.UDP_HC_C_ELIDED:
|
|
checksum = 0
|
|
|
|
return checksum
|
|
|
|
def parse(self, data, message_info):
|
|
udphc = LowpanUDPHC.from_bytes(bytearray(data.read(1)))
|
|
|
|
src_port, dst_port = self._decompress_udp_ports(udphc, data)
|
|
|
|
checksum = self._decompress_udp_checksum(udphc, data)
|
|
|
|
header = ipv6.UDPHeader(src_port, dst_port, checksum=checksum)
|
|
|
|
return header
|
|
|
|
|
|
class Context:
|
|
|
|
def __init__(self, prefix, prefix_length=None):
|
|
if isinstance(prefix, str):
|
|
prefix, prefix_length = prefix.split("/")
|
|
prefix_length = int(prefix_length)
|
|
|
|
a = ipaddress.ip_address(prefix)
|
|
|
|
self._prefix = bytearray(a.packed)
|
|
self._prefix_length = prefix_length
|
|
|
|
elif isinstance(prefix, bytearray):
|
|
self._prefix = prefix
|
|
self._prefix_length = (prefix_length if prefix_length is not None else len(self._prefix) * 8)
|
|
|
|
@property
|
|
def prefix(self):
|
|
return self._prefix[:self.prefix_length_all_bytes]
|
|
|
|
@property
|
|
def prefix_full_bytes(self):
|
|
return self._prefix[:self.prefix_length_full_bytes]
|
|
|
|
@property
|
|
def prefix_length(self):
|
|
return self._prefix_length
|
|
|
|
@property
|
|
def prefix_length_full_bytes(self):
|
|
return int(self._prefix_length / 8)
|
|
|
|
@property
|
|
def prefix_length_rest_bits(self):
|
|
return int(self._prefix_length % 8)
|
|
|
|
@property
|
|
def prefix_length_all_bytes(self):
|
|
if self.prefix_length_rest_bits > 0:
|
|
return self.prefix_length_full_bytes + 1
|
|
|
|
return self.prefix_length_full_bytes
|
|
|
|
|
|
class ContextManager(dict):
|
|
""" Class representing Context Manager. """
|
|
|
|
def __check_index(self, index):
|
|
if index < 0 or index > 15:
|
|
raise IndexError("Invalid index: {}. Valid index is in range [0, 15]".format(index))
|
|
|
|
def __check_type(self, value):
|
|
if not isinstance(value, Context):
|
|
raise TypeError("Invalid value type: {}".format(type(value)))
|
|
|
|
def __getitem__(self, index):
|
|
self.__check_index(index)
|
|
|
|
return super(ContextManager, self).__getitem__(index)
|
|
|
|
def __setitem__(self, index, value):
|
|
self.__check_index(index)
|
|
self.__check_type(value)
|
|
|
|
return super(ContextManager, self).__setitem__(index, value)
|
|
|
|
|
|
class LowpanIpv6HeaderFactory:
|
|
""" Factory that produces IPv6 header. """
|
|
|
|
IPV6_LINKLOCAL_PREFIX = bytearray([0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
|
|
|
|
SHORT_ADDR_PADDING_BYTES = bytearray([0x00, 0x00, 0x00, 0xff, 0xfe, 0x00])
|
|
|
|
IPHC_TF_4B = 0
|
|
IPHC_TF_3B = 1
|
|
IPHC_TF_1B = 2
|
|
IPHC_TF_ELIDED = 3
|
|
|
|
IPHC_NH_INLINE = 0
|
|
IPHC_NH_COMPRESSED = 1
|
|
|
|
IPHC_HLIM_CALCULATE = -1
|
|
IPHC_HLIM_INLINE = 0
|
|
IPHC_HLIM_1 = 1
|
|
IPHC_HLIM_64 = 2
|
|
IPHC_HLIM_255 = 3
|
|
|
|
IPHC_CID_CLEAR = 0
|
|
IPHC_CID_SET = 1
|
|
|
|
IPHC_SAC_STATELESS = 0
|
|
IPHC_SAC_STATEFUL = 1
|
|
|
|
IPHC_SAM_128B = 0
|
|
IPHC_SAM_UNSPECIFIED = 0
|
|
IPHC_SAM_64B = 1
|
|
IPHC_SAM_16B = 2
|
|
IPHC_SAM_0B = 3
|
|
IPHC_SAM_ELIDED = 3
|
|
|
|
IPHC_M_NO = 0
|
|
IPHC_M_YES = 1
|
|
|
|
IPHC_DAC_STATELESS = 0
|
|
IPHC_DAC_STATEFUL = 1
|
|
|
|
IPHC_DAM_128B = 0
|
|
IPHC_DAM_64B = 1
|
|
IPHC_DAM_48B = 1
|
|
IPHC_DAM_32B = 2
|
|
IPHC_DAM_16B = 2
|
|
IPHC_DAM_8B = 3
|
|
IPHC_DAM_0B = 3
|
|
IPHC_DAM_ELIDED = 3
|
|
|
|
IPHC_M_DAM_00 = 0
|
|
IPHC_M_DAM_01 = 1
|
|
IPHC_M_DAM_10 = 2
|
|
IPHC_M_DAM_11 = 3
|
|
|
|
def __init__(self, context_manager=None):
|
|
self._context_manager = context_manager
|
|
|
|
def _flow_label(self, data_bytes):
|
|
flow_label = (data_bytes[0] & 0x0F) << 16
|
|
flow_label += data_bytes[1] << 8
|
|
flow_label += data_bytes[2]
|
|
return flow_label
|
|
|
|
def _traffic_class(self, dscp, ecn):
|
|
return (dscp << 2) | ecn
|
|
|
|
def _unpack_dscp(self, data_byte):
|
|
return data_byte & 0x3F
|
|
|
|
def _unpack_ecn(self, data_byte):
|
|
return data_byte >> 6
|
|
|
|
def _decompress_tf_4bytes(self, data):
|
|
data_bytes = [b for b in bytearray(data.read(4))]
|
|
|
|
dscp = self._unpack_dscp(data_bytes[0])
|
|
ecn = self._unpack_ecn(data_bytes[0])
|
|
|
|
traffic_class = self._traffic_class(dscp, ecn)
|
|
flow_label = self._flow_label(data_bytes[1:])
|
|
|
|
return traffic_class, flow_label
|
|
|
|
def _decompress_tf_3bytes(self, data):
|
|
data_bytes = [b for b in bytearray(data.read(3))]
|
|
|
|
ecn = self._unpack_ecn(data_bytes[0])
|
|
|
|
traffic_class = self._traffic_class(dscp=0, ecn=ecn)
|
|
flow_label = self._flow_label(data_bytes)
|
|
|
|
return traffic_class, flow_label
|
|
|
|
def _decompress_tf_1byte(self, data):
|
|
data_byte = ord(data.read(1))
|
|
|
|
dscp = self._unpack_dscp(data_byte)
|
|
ecn = self._unpack_ecn(data_byte)
|
|
|
|
traffic_class = self._traffic_class(dscp, ecn)
|
|
flow_label = 0
|
|
|
|
return traffic_class, flow_label
|
|
|
|
def _decompress_tf(self, iphc, data):
|
|
if iphc.tf == self.IPHC_TF_4B:
|
|
return self._decompress_tf_4bytes(data)
|
|
|
|
elif iphc.tf == self.IPHC_TF_3B:
|
|
return self._decompress_tf_3bytes(data)
|
|
|
|
elif iphc.tf == self.IPHC_TF_1B:
|
|
return self._decompress_tf_1byte(data)
|
|
|
|
elif iphc.tf == self.IPHC_TF_ELIDED:
|
|
return 0, 0
|
|
|
|
def _decompress_nh(self, hc, data):
|
|
if hc.nh == self.IPHC_NH_INLINE:
|
|
return ord(data.read(1))
|
|
|
|
elif hc.nh == self.IPHC_NH_COMPRESSED:
|
|
return None
|
|
|
|
def _decompress_hlim(self, iphc, data):
|
|
if iphc.hlim == self.IPHC_HLIM_INLINE:
|
|
return ord(data.read(1))
|
|
|
|
elif iphc.hlim == self.IPHC_HLIM_1:
|
|
return 1
|
|
|
|
elif iphc.hlim == self.IPHC_HLIM_64:
|
|
return 64
|
|
|
|
elif iphc.hlim == self.IPHC_HLIM_255:
|
|
return 255
|
|
|
|
def _decompress_cid(self, iphc, data):
|
|
if iphc.cid == self.IPHC_CID_SET:
|
|
cid = ord(data.read(1))
|
|
|
|
sci = (cid >> 4) & 0x0F
|
|
dci = cid & 0x0F
|
|
|
|
return sci, dci
|
|
|
|
elif iphc.cid == self.IPHC_CID_CLEAR:
|
|
return 0, 0
|
|
|
|
def _decompress_src_addr_stateless(self, iphc, src_mac_addr, data):
|
|
if iphc.sam == self.IPHC_SAM_128B:
|
|
return bytearray(data.read(16))
|
|
|
|
elif iphc.sam == self.IPHC_SAM_64B:
|
|
return self.IPV6_LINKLOCAL_PREFIX + bytearray(data.read(8))
|
|
|
|
elif iphc.sam == self.IPHC_SAM_16B:
|
|
return (self.IPV6_LINKLOCAL_PREFIX + self.SHORT_ADDR_PADDING_BYTES + bytearray(data.read(2)))
|
|
|
|
elif iphc.sam == self.IPHC_SAM_ELIDED:
|
|
return self.IPV6_LINKLOCAL_PREFIX + src_mac_addr.convert_to_iid()
|
|
|
|
def _merge_prefix_with_address(self, prefix, prefix_length, address_bytes):
|
|
required_bytes = 16
|
|
|
|
prefix_length_full_bytes = int(prefix_length / 8)
|
|
prefix_length_rest_bits = int(prefix_length % 8)
|
|
|
|
prefix_length_all_bytes = prefix_length_full_bytes
|
|
|
|
if prefix_length_rest_bits > 0:
|
|
prefix_length_all_bytes += 1
|
|
|
|
# Case in which some bytes overlap
|
|
if (prefix_length_all_bytes + len(address_bytes)) > required_bytes:
|
|
###################################################################
|
|
# Example:
|
|
#
|
|
# Total address length: 128 bits
|
|
# * prefix length: 68 bits
|
|
# * address length: 64 bits
|
|
#
|
|
# overlap: 4 bits ==> the last 4 bits of the address must be replaced by the last 4 bits of prefix
|
|
#
|
|
# Result:
|
|
# +--------------------+---------------------+
|
|
# | prefix (68 bits) | address (64 bits) |
|
|
# +--------------------+---------------------+
|
|
###################################################################
|
|
|
|
src_addr = prefix[:prefix_length_full_bytes]
|
|
required_bytes -= prefix_length_full_bytes
|
|
|
|
if prefix_length_rest_bits > 0:
|
|
prefix_overlapping_byte = prefix[prefix_length_all_bytes - 1]
|
|
address_overlapping_byte = address_bytes[-required_bytes]
|
|
|
|
overlapping_byte = prefix_overlapping_byte & ~(0xff >> prefix_length_rest_bits)
|
|
overlapping_byte |= address_overlapping_byte & (0xff >> prefix_length_rest_bits)
|
|
|
|
src_addr += bytearray([overlapping_byte])
|
|
required_bytes -= 1
|
|
|
|
if required_bytes > 0:
|
|
src_addr += address_bytes[-required_bytes:]
|
|
|
|
else:
|
|
required_bytes -= prefix_length_all_bytes
|
|
required_bytes -= len(address_bytes)
|
|
|
|
src_addr = (prefix[:prefix_length_all_bytes] + bytearray([0x00] * required_bytes) + address_bytes)
|
|
|
|
return src_addr
|
|
|
|
def _decompress_src_addr_stateful(self, iphc, src_mac_addr, sci, data):
|
|
if iphc.sam == self.IPHC_SAM_UNSPECIFIED:
|
|
return bytearray([0x00] * 16)
|
|
|
|
elif iphc.sam == self.IPHC_SAM_64B:
|
|
context = self._context_manager[sci]
|
|
|
|
return self._merge_prefix_with_address(
|
|
prefix=context.prefix,
|
|
prefix_length=context.prefix_length,
|
|
address_bytes=bytearray(data.read(8)),
|
|
)
|
|
|
|
elif iphc.sam == self.IPHC_SAM_16B:
|
|
context = self._context_manager[sci]
|
|
address_bytes = self.SHORT_ADDR_PADDING_BYTES + bytearray(data.read(2))
|
|
|
|
return self._merge_prefix_with_address(
|
|
prefix=context.prefix,
|
|
prefix_length=context.prefix_length,
|
|
address_bytes=address_bytes,
|
|
)
|
|
|
|
elif iphc.sam == self.IPHC_SAM_0B:
|
|
context = self._context_manager[sci]
|
|
|
|
return self._merge_prefix_with_address(
|
|
prefix=context.prefix,
|
|
prefix_length=context.prefix_length,
|
|
address_bytes=src_mac_addr.convert_to_iid(),
|
|
)
|
|
|
|
def _decompress_src_addr(self, iphc, src_mac_addr, sci, data):
|
|
if iphc.sac == self.IPHC_SAC_STATELESS:
|
|
return self._decompress_src_addr_stateless(iphc, src_mac_addr, data)
|
|
|
|
elif iphc.sac == self.IPHC_SAC_STATEFUL:
|
|
return self._decompress_src_addr_stateful(iphc, src_mac_addr, sci, data)
|
|
|
|
def _decompress_unicast_dst_addr_stateless(self, iphc, dst_mac_addr, data):
|
|
if iphc.dam == self.IPHC_DAM_128B:
|
|
return bytearray(data.read(16))
|
|
|
|
elif iphc.dam == self.IPHC_DAM_64B:
|
|
return self.IPV6_LINKLOCAL_PREFIX + bytearray(data.read(8))
|
|
|
|
elif iphc.dam == self.IPHC_DAM_16B:
|
|
return (self.IPV6_LINKLOCAL_PREFIX + self.SHORT_ADDR_PADDING_BYTES + bytearray(data.read(2)))
|
|
|
|
elif iphc.dam == self.IPHC_DAM_ELIDED:
|
|
return self.IPV6_LINKLOCAL_PREFIX + dst_mac_addr.convert_to_iid()
|
|
|
|
def _decompress_unicast_dst_addr_stateful(self, iphc, dst_mac_addr, dci, data):
|
|
if iphc.dam == self.IPHC_DAM_128B:
|
|
raise RuntimeError("Reserved")
|
|
|
|
elif iphc.dam == self.IPHC_DAM_64B:
|
|
context = self._context_manager[dci]
|
|
|
|
return self._merge_prefix_with_address(
|
|
prefix=context.prefix,
|
|
prefix_length=context.prefix_length,
|
|
address_bytes=bytearray(data.read(8)),
|
|
)
|
|
|
|
elif iphc.dam == self.IPHC_DAM_16B:
|
|
context = self._context_manager[dci]
|
|
address_bytes = self.SHORT_ADDR_PADDING_BYTES + bytearray(data.read(2))
|
|
|
|
return self._merge_prefix_with_address(
|
|
prefix=context.prefix,
|
|
prefix_length=context.prefix_length,
|
|
address_bytes=address_bytes,
|
|
)
|
|
|
|
elif iphc.dam == self.IPHC_DAM_0B:
|
|
context = self._context_manager[dci]
|
|
|
|
return self._merge_prefix_with_address(
|
|
prefix=context.prefix,
|
|
prefix_length=context.prefix_length,
|
|
address_bytes=dst_mac_addr.convert_to_iid(),
|
|
)
|
|
|
|
def _decompress_unicast_dst_addr(self, iphc, dst_mac_addr, dci, data):
|
|
if iphc.dac == self.IPHC_DAC_STATELESS:
|
|
return self._decompress_unicast_dst_addr_stateless(iphc, dst_mac_addr, data)
|
|
|
|
elif iphc.dac == self.IPHC_DAC_STATEFUL:
|
|
return self._decompress_unicast_dst_addr_stateful(iphc, dst_mac_addr, dci, data)
|
|
|
|
def _decompress_multicast_dst_addr_stateless(self, iphc, data):
|
|
if iphc.dam == self.IPHC_DAM_128B:
|
|
return bytearray(data.read(16))
|
|
|
|
elif iphc.dam == self.IPHC_DAM_48B:
|
|
addr48b = bytearray(data.read(6))
|
|
return (bytearray([0xff, addr48b[0]]) + bytearray([0x00] * 9) + addr48b[1:])
|
|
|
|
elif iphc.dam == self.IPHC_DAM_32B:
|
|
addr32b = bytearray(data.read(4))
|
|
return (bytearray([0xff, addr32b[0]]) + bytearray([0x00] * 11) + addr32b[1:])
|
|
|
|
elif iphc.dam == self.IPHC_DAM_8B:
|
|
return (bytearray([0xff, 0x02]) + bytearray([0x00] * 13) + data.read(1))
|
|
|
|
def _decompress_multicast_dst_addr_stateful(self, iphc, dci, data):
|
|
if iphc.dam == self.IPHC_M_DAM_00:
|
|
context = self._context_manager[dci]
|
|
|
|
addr48b = bytearray(data.read(6))
|
|
|
|
p_bytes_count = 8
|
|
|
|
prefix = context.prefix[:p_bytes_count]
|
|
prefix_length = context.prefix_length
|
|
|
|
missing_bytes = p_bytes_count - len(prefix)
|
|
|
|
if missing_bytes > 0:
|
|
prefix += bytearray([0x00] * missing_bytes)
|
|
|
|
return (bytearray([0xff]) + addr48b[:2] + bytearray([prefix_length]) + prefix + addr48b[2:])
|
|
|
|
elif iphc.dam == self.IPHC_M_DAM_01:
|
|
raise RuntimeError("Reserved")
|
|
|
|
elif iphc.dam == self.IPHC_M_DAM_10:
|
|
raise RuntimeError("Reserved")
|
|
|
|
elif iphc.dam == self.IPHC_M_DAM_11:
|
|
raise RuntimeError("Reserved")
|
|
|
|
def _decompress_multicast_dst_addr(self, iphc, dci, data):
|
|
if iphc.dac == self.IPHC_DAC_STATELESS:
|
|
return self._decompress_multicast_dst_addr_stateless(iphc, data)
|
|
|
|
elif iphc.dac == self.IPHC_DAC_STATEFUL:
|
|
return self._decompress_multicast_dst_addr_stateful(iphc, dci, data)
|
|
|
|
def _decompress_dst_addr(self, iphc, dst_mac_addr, dci, data):
|
|
if iphc.m == self.IPHC_M_NO:
|
|
return self._decompress_unicast_dst_addr(iphc, dst_mac_addr, dci, data)
|
|
|
|
elif iphc.m == self.IPHC_M_YES:
|
|
return self._decompress_multicast_dst_addr(iphc, dci, data)
|
|
|
|
def set_lowpan_context(self, cid, prefix):
|
|
self._context_manager[cid] = Context(prefix)
|
|
|
|
def parse(self, data, message_info):
|
|
iphc = LowpanIPHC.from_bytes(bytearray(data.read(2)))
|
|
|
|
sci, dci = self._decompress_cid(iphc, data)
|
|
|
|
traffic_class, flow_label = self._decompress_tf(iphc, data)
|
|
|
|
next_header = self._decompress_nh(iphc, data)
|
|
|
|
hop_limit = self._decompress_hlim(iphc, data)
|
|
|
|
src_address = self._decompress_src_addr(iphc, message_info.source_mac_address, sci, data)
|
|
|
|
dst_address = self._decompress_dst_addr(iphc, message_info.destination_mac_address, dci, data)
|
|
|
|
header = ipv6.IPv6Header(src_address, dst_address, traffic_class, flow_label, hop_limit)
|
|
|
|
header.next_header = next_header
|
|
|
|
return header
|
|
|
|
|
|
class LowpanDecompressor:
|
|
""" Class decompressing 6LoWPAN packets. """
|
|
|
|
def __init__(
|
|
self,
|
|
lowpan_ip_header_factory,
|
|
lowpan_extension_headers_factory,
|
|
lowpan_udp_header_factory,
|
|
):
|
|
self._lowpan_ip_header_factory = lowpan_ip_header_factory
|
|
self._lowpan_extension_headers_factory = (lowpan_extension_headers_factory)
|
|
self._lowpan_udp_header_factory = lowpan_udp_header_factory
|
|
|
|
def _is_ipv6_extension_header(self, header_first_byte):
|
|
return ((header_first_byte >> 4) & 0x0F) == 0x0E
|
|
|
|
def _is_udp_header(self, header_first_byte):
|
|
return ((header_first_byte >> 4) & 0x0F) == 0x0F
|
|
|
|
def _peek_n_bytes(self, data, n):
|
|
read_data = data.read(n)
|
|
data.seek(-n, io.SEEK_CUR)
|
|
return read_data
|
|
|
|
def _is_next_header_compressed(self, header):
|
|
return header.next_header is None
|
|
|
|
def set_lowpan_context(self, cid, prefix):
|
|
self._lowpan_ip_header_factory.set_lowpan_context(cid, prefix)
|
|
|
|
def decompress(self, data, message_info):
|
|
ipv6_header = self._lowpan_ip_header_factory.parse(data, message_info)
|
|
|
|
previous_header = ipv6_header
|
|
|
|
extension_headers = []
|
|
udp_header = None
|
|
|
|
if self._is_next_header_compressed(ipv6_header):
|
|
|
|
while data.tell() < len(data.getvalue()):
|
|
header_first_byte = ord(self._peek_n_bytes(data, 1))
|
|
|
|
if self._is_ipv6_extension_header(header_first_byte):
|
|
extension_header = self._lowpan_extension_headers_factory.parse(data, message_info)
|
|
extension_headers.append(extension_header)
|
|
|
|
# Update next header field in the previous header
|
|
previous_header.next_header = extension_header.type
|
|
previous_header = extension_header
|
|
|
|
if not self._is_next_header_compressed(extension_header):
|
|
# There is not more compressed headers
|
|
break
|
|
|
|
elif self._is_udp_header(header_first_byte):
|
|
udp_header = self._lowpan_udp_header_factory.parse(data, message_info)
|
|
|
|
# Update next header field in the previous header
|
|
previous_header.next_header = udp_header.type
|
|
|
|
# There is not more headers after UDP header
|
|
break
|
|
|
|
return ipv6_header, extension_headers, udp_header
|
|
|
|
|
|
class LowpanMeshHeader(object):
|
|
""" Class representing 6LoWPAN mesh header (RFC 4944 5.2). """
|
|
|
|
def __init__(self, hops_left, originator_address, final_destination_address):
|
|
self._hops_left = hops_left
|
|
self._originator_address = originator_address
|
|
self._final_destination_address = final_destination_address
|
|
|
|
@property
|
|
def hops_left(self):
|
|
return self._hops_left
|
|
|
|
@property
|
|
def originator_address(self):
|
|
return self._originator_address
|
|
|
|
@property
|
|
def final_destination_address(self):
|
|
return self._final_destination_address
|
|
|
|
|
|
class LowpanMeshHeaderFactory:
|
|
|
|
def _parse_address(self, data, is_short):
|
|
if is_short:
|
|
return common.MacAddress.from_rloc16(bytearray(data.read(2)))
|
|
else:
|
|
return common.MacAddress.from_eui64(bytearray(data.read(8)))
|
|
|
|
def parse(self, data, message_info):
|
|
data_byte = ord(data.read(1))
|
|
|
|
is_short_originator_address = bool(data_byte & 0x20)
|
|
is_short_final_destination_address = bool(data_byte & 0x10)
|
|
|
|
if (data_byte & 0x0F) != 0x0F:
|
|
hops_left = data_byte & 0x0F
|
|
else:
|
|
hops_left = ord(data.read(1))
|
|
|
|
originator_address = self._parse_address(data, is_short_originator_address)
|
|
final_destination_address = self._parse_address(data, is_short_final_destination_address)
|
|
|
|
return LowpanMeshHeader(hops_left, originator_address, final_destination_address)
|
|
|
|
|
|
class LowpanFragmentationHeader(object):
|
|
|
|
def __init__(self, datagram_size, datagram_tag, datagram_offset=0):
|
|
self._datagram_size = datagram_size
|
|
self._datagram_tag = datagram_tag
|
|
self._datagram_offset = datagram_offset
|
|
|
|
@property
|
|
def datagram_size(self):
|
|
return self._datagram_size
|
|
|
|
@property
|
|
def datagram_tag(self):
|
|
return self._datagram_tag
|
|
|
|
@property
|
|
def datagram_offset(self):
|
|
return self._datagram_offset
|
|
|
|
@property
|
|
def is_first(self):
|
|
return self.datagram_offset == 0
|
|
|
|
@classmethod
|
|
def from_bytes(cls, data):
|
|
datagram_size = struct.unpack(">H", data.read(2))[0]
|
|
has_offset = ((datagram_size >> 11) & 0x1F) == 0x1C
|
|
|
|
datagram_size &= 0x7FF
|
|
datagram_tag = struct.unpack(">H", data.read(2))[0]
|
|
datagram_offset = 0
|
|
|
|
if has_offset:
|
|
datagram_offset = ord(data.read(1))
|
|
|
|
return cls(datagram_size, datagram_tag, datagram_offset)
|
|
|
|
|
|
class LowpanFragmentsBuffer(object):
|
|
|
|
def __init__(self, buffer_size):
|
|
self._buffer = [None] * buffer_size
|
|
self._position = 0
|
|
|
|
def write(self, data):
|
|
if (self._position + len(data)) > len(self._buffer):
|
|
raise ValueError("Write failure. Data length is bigger than the destination buffer length.")
|
|
|
|
for i, byte in enumerate(data):
|
|
self._buffer[self._position + i] = byte
|
|
|
|
self._position += len(data)
|
|
return len(data)
|
|
|
|
def seek(self, offset):
|
|
if offset >= len(self._buffer):
|
|
raise ValueError("Could not seek current offset. Offset value is bigger than the buffer length.")
|
|
|
|
self._position = offset
|
|
|
|
def tell(self):
|
|
return self._position
|
|
|
|
def whole_packet_received(self):
|
|
return all([byte is not None for byte in self._buffer])
|
|
|
|
def read(self):
|
|
if not self.whole_packet_received():
|
|
raise ValueError("Only a part of the packet has been stored in the buffer.")
|
|
|
|
return bytearray(self._buffer)
|
|
|
|
def __len__(self):
|
|
return len(self._buffer)
|
|
|
|
|
|
class LowpanFragmentsBuffersManager(object):
|
|
|
|
def __init__(self):
|
|
self._fragments_buffers = {}
|
|
|
|
def _create_key(self, message_info, datagram_tag):
|
|
key = (bytes(message_info.source_mac_address.mac_address) +
|
|
bytes(message_info.destination_mac_address.mac_address) + bytes(datagram_tag))
|
|
return key
|
|
|
|
def _allocate_fragments_buffer(self, key, datagram_size):
|
|
if datagram_size is None or datagram_size < 0:
|
|
raise ValueError("Could not allocate fragments buffer. Invalid datagram size: {}".format(datagram_size))
|
|
|
|
fragments_buffer = LowpanFragmentsBuffer(datagram_size)
|
|
|
|
self._fragments_buffers[key] = fragments_buffer
|
|
return fragments_buffer
|
|
|
|
def get_fragments_buffer(self, message_info, datagram_tag, datagram_size=None):
|
|
key = self._create_key(message_info, datagram_tag)
|
|
|
|
if key not in self._fragments_buffers:
|
|
self._allocate_fragments_buffer(key, datagram_size)
|
|
|
|
return self._fragments_buffers[key]
|
|
|
|
def free_fragments_buffer(self, message_info, datagram_tag):
|
|
key = self._create_key(message_info, datagram_tag)
|
|
|
|
del self._fragments_buffers[key]
|
|
|
|
|
|
class LowpanParser(object):
|
|
|
|
def __init__(
|
|
self,
|
|
lowpan_mesh_header_factory,
|
|
lowpan_decompressor,
|
|
lowpan_fragements_buffers_manager,
|
|
ipv6_packet_factory,
|
|
):
|
|
self._lowpan_mesh_header_factory = lowpan_mesh_header_factory
|
|
self._lowpan_decompressor = lowpan_decompressor
|
|
self._lowpan_fragments_buffers_manager = (lowpan_fragements_buffers_manager)
|
|
self._ipv6_packet_factory = ipv6_packet_factory
|
|
|
|
def _peek_n_bytes(self, data, n):
|
|
data_bytes = data.read(n)
|
|
data.seek(-n, io.SEEK_CUR)
|
|
return data_bytes
|
|
|
|
def _is_mesh_header(self, first_byte):
|
|
return ((first_byte >> 6) & 0x03) == 0x02
|
|
|
|
def _is_first_fragmentation_header(self, first_byte):
|
|
return ((first_byte >> 3) & 0x1F) == 0x18
|
|
|
|
def _is_subsequent_fragmentation_header(self, first_byte):
|
|
return ((first_byte >> 3) & 0x1F) == 0x1C
|
|
|
|
def _is_iphc(self, first_byte):
|
|
return ((first_byte >> 5) & 0x07) == 0x03
|
|
|
|
def _decompress_iphc(self, data, message_info):
|
|
return self._lowpan_decompressor.decompress(data, message_info)
|
|
|
|
def _handle_first_fragmentation_header(self, data, message_info):
|
|
fragmentation_header = LowpanFragmentationHeader.from_bytes(data)
|
|
|
|
fragments_buffer = self._lowpan_fragments_buffers_manager.get_fragments_buffer(
|
|
message_info,
|
|
fragmentation_header.datagram_tag,
|
|
fragmentation_header.datagram_size,
|
|
)
|
|
|
|
ipv6_header, extension_headers, udp_header = self._decompress_iphc(data, message_info)
|
|
|
|
uncompressed_data = data.read()
|
|
|
|
# Update payload lengths
|
|
ipv6_header.payload_length = fragmentation_header.datagram_size - len(ipv6_header)
|
|
|
|
fragments_buffer.seek(0)
|
|
fragments_buffer.write(ipv6_header.to_bytes())
|
|
|
|
for extension_header in extension_headers:
|
|
fragments_buffer.write(extension_header.to_bytes())
|
|
|
|
if udp_header is not None:
|
|
fragments_buffer.write(udp_header.to_bytes())
|
|
|
|
fragments_buffer.write(uncompressed_data)
|
|
|
|
if fragments_buffer.whole_packet_received():
|
|
data = io.BytesIO(fragments_buffer.read())
|
|
|
|
self._lowpan_fragments_buffers_manager.free_fragments_buffer(message_info,
|
|
fragmentation_header.datagram_tag)
|
|
|
|
return self._ipv6_packet_factory.parse(data, message_info)
|
|
|
|
return None
|
|
|
|
def _handle_subsequent_fragmentation_header(self, data, message_info):
|
|
fragmentation_header = LowpanFragmentationHeader.from_bytes(data)
|
|
|
|
fragments_buffer = self._lowpan_fragments_buffers_manager.get_fragments_buffer(
|
|
message_info,
|
|
fragmentation_header.datagram_tag,
|
|
fragmentation_header.datagram_size,
|
|
)
|
|
|
|
offset = fragmentation_header.datagram_offset * 8
|
|
|
|
fragments_buffer.seek(offset)
|
|
fragments_buffer.write(data.read())
|
|
|
|
if fragments_buffer.whole_packet_received():
|
|
data = io.BytesIO(fragments_buffer.read())
|
|
|
|
self._lowpan_fragments_buffers_manager.free_fragments_buffer(message_info,
|
|
fragmentation_header.datagram_tag)
|
|
|
|
return self._ipv6_packet_factory.parse(data, message_info)
|
|
|
|
return None
|
|
|
|
def _handle_iphc_header(self, data, message_info):
|
|
ipv6_header, extension_headers, udp_header = self._decompress_iphc(data, message_info)
|
|
|
|
uncompressed_data = data.read()
|
|
|
|
decompressed_data = bytearray([])
|
|
|
|
for extension_header in extension_headers:
|
|
decompressed_data += extension_header.to_bytes()
|
|
|
|
if udp_header is not None:
|
|
udp_header.payload_length = len(uncompressed_data)
|
|
|
|
decompressed_data += udp_header.to_bytes()
|
|
|
|
decompressed_data += uncompressed_data
|
|
|
|
ipv6_header.payload_length = len(decompressed_data)
|
|
|
|
decompressed_data = ipv6_header.to_bytes() + decompressed_data
|
|
|
|
return self._ipv6_packet_factory.parse(io.BytesIO(decompressed_data), message_info)
|
|
|
|
def set_lowpan_context(self, cid, prefix):
|
|
self._lowpan_decompressor.set_lowpan_context(cid, prefix)
|
|
|
|
def parse(self, data, message_info):
|
|
|
|
while data.tell() < len(data.getvalue()):
|
|
first_byte = ord(self._peek_n_bytes(data, n=1))
|
|
|
|
if self._is_mesh_header(first_byte):
|
|
mesh_header = self._lowpan_mesh_header_factory.parse(data, message_info)
|
|
|
|
message_info.source_mac_address = (mesh_header.originator_address)
|
|
message_info.destination_mac_address = (mesh_header.final_destination_address)
|
|
|
|
elif self._is_first_fragmentation_header(first_byte):
|
|
return self._handle_first_fragmentation_header(data, message_info)
|
|
|
|
elif self._is_subsequent_fragmentation_header(first_byte):
|
|
return self._handle_subsequent_fragmentation_header(data, message_info)
|
|
|
|
elif self._is_iphc(first_byte):
|
|
return self._handle_iphc_header(data, message_info)
|
|
|
|
else:
|
|
raise RuntimeError("Unsupported header type: 0x{:02x}".format(first_byte))
|