Files
openthread/tests/scripts/thread-cert/lowpan.py
T
Abtin Keshavarzian 71e00eb20f [test] add missing tests in the Makefile.am & update file access mode (#6825)
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").
2021-07-19 14:35:50 -07:00

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))