762 lines
26 KiB
Python
762 lines
26 KiB
Python
#!/usr/bin/env python3
|
|
#
|
|
# Copyright (c) 2019, 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 datetime
|
|
import sys
|
|
import time
|
|
from typing import Any, Union
|
|
|
|
from pyshark.packet.fields import LayerFieldsContainer, LayerField
|
|
from pyshark.packet.packet import Packet as RawPacket
|
|
|
|
from pktverify.addrs import EthAddr, ExtAddr, Ipv6Addr
|
|
from pktverify.bytes import Bytes
|
|
from pktverify.consts import VALID_LAYER_NAMES
|
|
from pktverify.null_field import nullField
|
|
|
|
|
|
def _auto(v: Union[LayerFieldsContainer, LayerField]):
|
|
"""parse the layer field automatically according to its format"""
|
|
assert not isinstance(v, LayerFieldsContainer) or len(v.fields) == 1 or v.get_default_value() is not None, v.fields
|
|
dv = v.get_default_value()
|
|
rv = v.raw_value
|
|
|
|
if dv.startswith('0x'):
|
|
return int(dv, 16)
|
|
|
|
try:
|
|
if dv == rv:
|
|
return int(dv)
|
|
elif int(dv) == int(rv, 16):
|
|
return int(dv)
|
|
except (ValueError, TypeError):
|
|
pass
|
|
|
|
if rv is None:
|
|
try:
|
|
return int(dv)
|
|
except (ValueError, TypeError):
|
|
pass
|
|
|
|
if ':' in dv and '::' not in dv and dv.replace(':', '') == rv: # '88:00', '8800'
|
|
return int(rv, 16)
|
|
|
|
# timestamp: 'Jan 1, 1970 08:00:00.000000000 CST', '0000000000000000'
|
|
# convert to seconds from 1970, ignore the nanosecond for now since
|
|
# there are integer seconds applied in the test cases
|
|
try:
|
|
time_str = datetime.datetime.strptime(dv, "%b %d, %Y %H:%M:%S.%f000 %Z")
|
|
time_in_sec = time.mktime(time_str.utctimetuple())
|
|
return int(time_in_sec)
|
|
except (ValueError, TypeError):
|
|
pass
|
|
|
|
try:
|
|
int(rv, 16)
|
|
return int(dv)
|
|
except Exception:
|
|
pass
|
|
|
|
raise ValueError((v, v.get_default_value(), v.raw_value))
|
|
|
|
|
|
def _payload(v: Union[LayerFieldsContainer, LayerField]) -> bytearray:
|
|
"""parse the layer field as a bytearray"""
|
|
assert not isinstance(v, LayerFieldsContainer) or len(v.fields) == 1
|
|
hex_value = v.raw_value
|
|
assert len(hex_value) % 2 == 0
|
|
s = bytearray()
|
|
for i in range(0, len(hex_value), 2):
|
|
s.append(int(hex_value[i:i + 2], 16))
|
|
|
|
return s
|
|
|
|
|
|
def _hex(v: Union[LayerFieldsContainer, LayerField]) -> int:
|
|
"""parse the layer field as a hex string"""
|
|
# split v into octets and reverse the order
|
|
assert not isinstance(v, LayerFieldsContainer) or len(v.fields) == 1
|
|
return int(v.get_default_value(), 16)
|
|
|
|
|
|
def _raw_hex(v: Union[LayerFieldsContainer, LayerField]) -> int:
|
|
"""parse the layer field as a raw hex string"""
|
|
# split v into octets and reverse the order
|
|
assert not isinstance(v, LayerFieldsContainer) or len(v.fields) == 1
|
|
iv = v.hex_value
|
|
|
|
try:
|
|
int(v.get_default_value())
|
|
assert int(v.get_default_value()) == iv, (v.get_default_value(), v.raw_value)
|
|
except ValueError:
|
|
pass
|
|
|
|
try:
|
|
int(v.get_default_value(), 16)
|
|
assert int(v.get_default_value(), 16) == iv, (v.get_default_value(), v.raw_value)
|
|
except ValueError:
|
|
pass
|
|
|
|
return iv
|
|
|
|
|
|
def _raw_hex_rev(v: Union[LayerFieldsContainer, LayerField]) -> int:
|
|
"""parse the layer field as a reversed raw hex string"""
|
|
# split v into octets and reverse the order
|
|
assert not isinstance(v, LayerFieldsContainer) or len(v.fields) == 1
|
|
rv = v.raw_value
|
|
octets = [rv[i:i + 2] for i in range(0, len(rv), 2)]
|
|
|
|
iv = int(''.join(reversed(octets)), 16)
|
|
|
|
try:
|
|
int(v.get_default_value())
|
|
assert int(v.get_default_value()) == iv, (v.get_default_value(), v.raw_value)
|
|
except ValueError:
|
|
pass
|
|
|
|
try:
|
|
int(v.get_default_value(), 16)
|
|
assert int(v.get_default_value(), 16) == iv, (v.get_default_value(), v.raw_value)
|
|
except ValueError:
|
|
pass
|
|
|
|
return iv
|
|
|
|
|
|
def _dec(v: Union[LayerFieldsContainer, LayerField]) -> int:
|
|
"""parse the layer field as a decimal"""
|
|
assert not isinstance(v, LayerFieldsContainer) or len(v.fields) == 1
|
|
return int(v.get_default_value())
|
|
|
|
|
|
def _float(v: Union[LayerFieldsContainer, LayerField]) -> float:
|
|
"""parse the layer field as a float"""
|
|
assert not isinstance(v, LayerFieldsContainer) or len(v.fields) == 1
|
|
return float(v.get_default_value())
|
|
|
|
|
|
def _str(v: Union[LayerFieldsContainer, LayerField]) -> str:
|
|
"""parse the layer field as a string"""
|
|
assert not isinstance(v, LayerFieldsContainer) or len(v.fields) == 1
|
|
return str(v.get_default_value())
|
|
|
|
|
|
def _bytes(v: Union[LayerFieldsContainer, LayerField]) -> Bytes:
|
|
"""parse the layer field as raw bytes"""
|
|
assert not isinstance(v, LayerFieldsContainer) or len(v.fields) == 1
|
|
return Bytes(v.raw_value)
|
|
|
|
|
|
def _ext_addr(v: Union[LayerFieldsContainer, LayerField]) -> ExtAddr:
|
|
"""parse the layer field as an extended address"""
|
|
assert not isinstance(v, LayerFieldsContainer) or len(v.fields) == 1
|
|
return ExtAddr(v.get_default_value())
|
|
|
|
|
|
def _ipv6_addr(v: Union[LayerFieldsContainer, LayerField]) -> Ipv6Addr:
|
|
"""parse the layer field as an IPv6 address"""
|
|
assert not isinstance(v, LayerFieldsContainer) or len(v.fields) == 1
|
|
return Ipv6Addr(v.get_default_value())
|
|
|
|
|
|
def _eth_addr(v: Union[LayerFieldsContainer, LayerField]) -> EthAddr:
|
|
"""parse the layer field as an Ethernet MAC address"""
|
|
assert not isinstance(v, LayerFieldsContainer) or len(v.fields) == 1, v.fields
|
|
return EthAddr(v.get_default_value())
|
|
|
|
|
|
def _routerid_set(v: Union[LayerFieldsContainer, LayerField]) -> set:
|
|
"""parse the layer field as a set of router ids
|
|
|
|
Notes: the router ID mask in wireshark is a
|
|
hexadecimal string separated by ':'
|
|
"""
|
|
assert not isinstance(v, LayerFieldsContainer) or len(v.fields) == 1
|
|
|
|
try:
|
|
ridmask = str(v.get_default_value())
|
|
assert isinstance(ridmask, str), ridmask
|
|
ridmask_int = int(ridmask.replace(':', ''), base=16)
|
|
rid_set = set()
|
|
count = 0
|
|
while ridmask_int:
|
|
count += 1
|
|
if ridmask_int & 1:
|
|
rid_set.add(64 - count)
|
|
ridmask_int = ridmask_int >> 1
|
|
except ValueError:
|
|
pass
|
|
|
|
return rid_set
|
|
|
|
|
|
class _first(object):
|
|
"""parse the first layer field"""
|
|
|
|
def __init__(self, sub_parse):
|
|
self._sub_parse = sub_parse
|
|
|
|
def __call__(self, v: Union[LayerFieldsContainer, LayerField]):
|
|
return self._sub_parse(v.fields[0])
|
|
|
|
|
|
class _list(object):
|
|
"""parse all layer fields into a list"""
|
|
|
|
def __init__(self, sub_parse):
|
|
self._sub_parse = sub_parse
|
|
|
|
def __call__(self, v: Union[LayerFieldsContainer, LayerField]):
|
|
return [self._sub_parse(f) for f in v.fields]
|
|
|
|
|
|
_LAYER_FIELDS = {
|
|
# WPAN
|
|
'wpan.fcf': _raw_hex_rev,
|
|
'wpan.cmd': _auto,
|
|
'wpan.security': _auto,
|
|
'wpan.frame_type': _auto,
|
|
'wpan.pending': _auto,
|
|
'wpan.ack_request': _auto,
|
|
'wpan.pan_id_compression': _auto,
|
|
'wpan.seqno_suppression': _auto,
|
|
'wpan.ie_present': _auto,
|
|
'wpan.dst_addr_mode': _auto,
|
|
'wpan.version': _auto,
|
|
'wpan.src_addr_mode': _auto,
|
|
'wpan.dst_pan': _auto,
|
|
'wpan.seq_no': _auto,
|
|
'wpan.src16': _auto,
|
|
'wpan.dst16': _auto,
|
|
'wpan.src64': _ext_addr,
|
|
'wpan.dst64': _ext_addr,
|
|
'wpan.fcs': _raw_hex_rev,
|
|
'wpan.fcs_ok': _auto,
|
|
'wpan.frame_length': _dec,
|
|
'wpan.key_number': _auto,
|
|
'wpan.aux_sec.sec_suite': _auto,
|
|
'wpan.aux_sec.security_control_field': _auto,
|
|
'wpan.aux_sec.sec_level': _auto,
|
|
'wpan.aux_sec.key_id_mode': _auto,
|
|
'wpan.aux_sec.frame_counter_suppression': _auto,
|
|
'wpan.aux_sec.asn_in_nonce': _auto,
|
|
'wpan.aux_sec.reserved': _auto,
|
|
'wpan.aux_sec.frame_counter': _auto,
|
|
'wpan.aux_sec.key_source': _auto,
|
|
'wpan.aux_sec.key_index': _auto,
|
|
'wpan.aux_sec.hdr': _str,
|
|
'wpan.mic': _auto,
|
|
'wpan.channel': _auto,
|
|
'wpan.header_ie.id': _list(_auto),
|
|
'wpan.header_ie.csl.period': _auto,
|
|
'wpan.payload_ie.vendor.oui': _auto,
|
|
|
|
# MLE
|
|
'mle.cmd': _auto,
|
|
'mle.sec_suite': _hex,
|
|
'mle.tlv.type': _list(_dec),
|
|
'mle.tlv.len': _list(_dec),
|
|
'mle.tlv.mode.receiver_on_idle': _auto,
|
|
'mle.tlv.mode.reserved1': _auto,
|
|
'mle.tlv.mode.reserved2': _auto,
|
|
'mle.tlv.mode.device_type_bit': _auto,
|
|
'mle.tlv.mode.network_data': _auto,
|
|
'mle.tlv.challenge': _bytes,
|
|
'mle.tlv.scan_mask.r': _auto,
|
|
'mle.tlv.scan_mask.e': _auto,
|
|
'mle.tlv.version': _auto,
|
|
'mle.tlv.source_addr': _auto,
|
|
'mle.tlv.active_tstamp': _auto,
|
|
'mle.tlv.pending_tstamp': _auto,
|
|
'mle.tlv.leader_data.partition_id': _auto,
|
|
'mle.tlv.leader_data.weighting': _auto,
|
|
'mle.tlv.leader_data.data_version': _auto,
|
|
'mle.tlv.leader_data.stable_data_version': _auto,
|
|
'mle.tlv.leader_data.router_id': _auto,
|
|
'mle.tlv.route64.nbr_out': _list(_auto),
|
|
'mle.tlv.route64.nbr_in': _list(_auto),
|
|
'mle.tlv.route64.id_seq': _auto,
|
|
'mle.tlv.route64.id_mask': _routerid_set,
|
|
'mle.tlv.route64.cost': _list(_auto),
|
|
'mle.tlv.response': _bytes,
|
|
'mle.tlv.mle_frm_cntr': _auto,
|
|
'mle.tlv.ll_frm_cntr': _auto,
|
|
'mle.tlv.link_margin': _auto,
|
|
'mle.tlv.conn.sed_dgram_cnt': _auto,
|
|
'mle.tlv.conn.sed_buf_size': _auto,
|
|
'mle.tlv.conn.lq3': _auto,
|
|
'mle.tlv.conn.lq2': _auto,
|
|
'mle.tlv.conn.lq1': _auto,
|
|
'mle.tlv.conn.leader_cost': _auto,
|
|
'mle.tlv.conn.id_seq': _auto,
|
|
'mle.tlv.conn.flags.pp': _auto,
|
|
'mle.tlv.conn.active_rtrs': _auto,
|
|
'mle.tlv.timeout': _auto,
|
|
'mle.tlv.addr16': _auto,
|
|
'mle.tlv.channel': _auto,
|
|
'mle.tlv.addr_reg_iid': _list(_auto),
|
|
'mle.tlv.link_enh_ack_flags': _auto,
|
|
'mle.tlv.link_forward_series': _list(_auto),
|
|
'mle.tlv.link_requested_type_id_flags': _list(_hex),
|
|
'mle.tlv.link_sub_tlv': _auto,
|
|
'mle.tlv.link_status_sub_tlv': _auto,
|
|
'mle.tlv.query_id': _auto,
|
|
'mle.tlv.metric_type_id_flags.type': _list(_hex),
|
|
'mle.tlv.metric_type_id_flags.metric': _list(_hex),
|
|
'mle.tlv.metric_type_id_flags.l': _list(_hex),
|
|
'mle.tlv.link_requested_type_id_flags': _bytes,
|
|
|
|
# IP
|
|
'ip.version': _auto,
|
|
'ip.src': _str,
|
|
'ip.src_host': _str,
|
|
'ip.dst': _str,
|
|
'ip.dst_host': _str,
|
|
'ip.ttl': _auto,
|
|
'ip.proto': _auto,
|
|
'ip.len': _auto,
|
|
'ip.id': _auto,
|
|
'ip.host': _list(_str),
|
|
'ip.hdr_len': _dec,
|
|
'ip.frag_offset': _auto,
|
|
'ip.flags.rb': _auto,
|
|
'ip.flags.mf': _auto,
|
|
'ip.flags.df': _auto,
|
|
'ip.dsfield.ecn': _auto,
|
|
'ip.dsfield.dscp': _auto,
|
|
'ip.checksum.status': _auto,
|
|
'ip.addr': _list(_str),
|
|
'ip.options.routeralert': _bytes,
|
|
'ip.opt.type.number': _auto,
|
|
'ip.opt.type.copy': _auto,
|
|
'ip.opt.type.class': _auto,
|
|
'ip.opt.ra': _auto,
|
|
'ip.opt.len': _auto,
|
|
# UDP
|
|
'udp.stream': _auto,
|
|
'udp.srcport': _auto,
|
|
'udp.dstport': _auto,
|
|
'udp.length': _auto,
|
|
'udp.port': _list(_dec),
|
|
'udp.checksum.status': _auto,
|
|
|
|
# IPv6
|
|
'ipv6.version': _auto,
|
|
'ipv6.src': _ipv6_addr,
|
|
'ipv6.src_host': _ipv6_addr,
|
|
'ipv6.dst': _ipv6_addr,
|
|
'ipv6.dst_host': _ipv6_addr,
|
|
'ipv6.addr': _list(_ipv6_addr),
|
|
'ipv6.tclass.dscp': _auto,
|
|
'ipv6.tclass.ecn': _auto,
|
|
'ipv6.flow': _auto,
|
|
'ipv6.hlim': _auto,
|
|
'ipv6.nxt': _auto,
|
|
'ipv6.hopopts.len': _auto,
|
|
'ipv6.hopopts.nxt': _auto,
|
|
'ipv6.hopopts.len_oct': _dec,
|
|
'ipv6.host': _list(_ipv6_addr),
|
|
'ipv6.plen': _auto,
|
|
'ipv6.opt.type.rest': _list(_auto),
|
|
'ipv6.opt.type.change': _list(_auto),
|
|
'ipv6.opt.type.action': _list(_auto),
|
|
'ipv6.opt.router_alert': _auto,
|
|
'ipv6.opt.padn': _str,
|
|
'ipv6.opt.length': _list(_auto),
|
|
'ipv6.opt.mpl.seed_id': _bytes,
|
|
'ipv6.opt.mpl.sequence': _auto,
|
|
'ipv6.opt.mpl.flag.v': _auto,
|
|
'ipv6.opt.mpl.flag.s': _auto,
|
|
'ipv6.opt.mpl.flag.rsv': _auto,
|
|
'ipv6.opt.mpl.flag.m': _auto,
|
|
|
|
# Eth
|
|
'eth.src': _eth_addr,
|
|
'eth.src_resolved': _eth_addr,
|
|
'eth.dst': _eth_addr,
|
|
'eth.dst_resolved': _eth_addr,
|
|
'eth.type': _auto,
|
|
'eth.addr': _list(_eth_addr),
|
|
'eth.addr_resolved': _list(_eth_addr),
|
|
'eth.ig': _list(_auto),
|
|
'eth.lg': _list(_auto),
|
|
# 6LOWPAN
|
|
'6lowpan.src': _ipv6_addr,
|
|
'6lowpan.dst': _ipv6_addr,
|
|
'6lowpan.udp.src': _auto,
|
|
'6lowpan.udp.dst': _auto,
|
|
'6lowpan.udp.checksum': _auto,
|
|
'6lowpan.frag.offset': _auto,
|
|
'6lowpan.frag.tag': _auto,
|
|
'6lowpan.frag.size': _auto,
|
|
'6lowpan.pattern': _list(_auto),
|
|
'6lowpan.hops': _auto,
|
|
'6lowpan.padding': _auto,
|
|
'6lowpan.next': _auto,
|
|
'6lowpan.flow': _auto,
|
|
'6lowpan.ecn': _auto,
|
|
'6lowpan.iphc.tf': _auto,
|
|
'6lowpan.iphc.m': _auto,
|
|
'6lowpan.iphc.nh': _auto,
|
|
'6lowpan.iphc.hlim': _auto,
|
|
'6lowpan.iphc.cid': _auto,
|
|
'6lowpan.iphc.sac': _auto,
|
|
'6lowpan.iphc.sam': _auto,
|
|
'6lowpan.iphc.dac': _auto,
|
|
'6lowpan.iphc.dam': _auto,
|
|
'6lowpan.iphc.sci': _auto,
|
|
'6lowpan.iphc.dci': _auto,
|
|
'6lowpan.iphc.sctx.prefix': _ipv6_addr,
|
|
'6lowpan.iphc.dctx.prefix': _ipv6_addr,
|
|
'6lowpan.mesh.v': _auto,
|
|
'6lowpan.nhc.pattern': _list(_auto),
|
|
'6lowpan.nhc.udp.checksum': _auto,
|
|
'6lowpan.nhc.udp.ports': _auto,
|
|
'6lowpan.nhc.ext.nh': _auto,
|
|
'6lowpan.nhc.ext.length': _auto,
|
|
'6lowpan.nhc.ext.eid': _auto,
|
|
'6lowpan.reassembled.length': _auto,
|
|
'6lowpan.fragments': _str,
|
|
'6lowpan.fragment.count': _auto,
|
|
'6lowpan.mesh.orig16': _auto,
|
|
'6lowpan.mesh.hops8': _auto,
|
|
'6lowpan.mesh.hops': _auto,
|
|
'6lowpan.mesh.f': _auto,
|
|
'6lowpan.mesh.dest16': _auto,
|
|
|
|
# ICMPv6
|
|
'icmpv6.type': _first(_auto),
|
|
'icmpv6.code': _first(_auto),
|
|
'icmpv6.checksum': _first(_auto),
|
|
'icmpv6.reserved': _raw_hex,
|
|
'icmpv6.resptime': _float,
|
|
'icmpv6.resp_to': _auto,
|
|
'icmpv6.mldr.nb_mcast_records': _auto,
|
|
'icmpv6.nd.ra.cur_hop_limit': _auto,
|
|
'icmpv6.nd.ns.target_address': _ipv6_addr,
|
|
'icmpv6.nd.na.target_address': _ipv6_addr,
|
|
'icmpv6.nd.na.flag.s': _auto,
|
|
'icmpv6.nd.na.flag.o': _auto,
|
|
'icmpv6.nd.na.flag.r': _auto,
|
|
'icmpv6.nd.na.flag.rsv': _auto,
|
|
'icmpv6.mldr.mar.record_type': _list(_auto),
|
|
'icmpv6.mldr.mar.aux_data_len': _list(_auto),
|
|
'icmpv6.mldr.mar.nb_sources': _list(_auto),
|
|
'icmpv6.mldr.mar.multicast_address': _list(_ipv6_addr),
|
|
'icmpv6.opt.type': _list(_auto),
|
|
'icmpv6.opt.nonce': _bytes,
|
|
'icmpv6.opt.linkaddr': _eth_addr,
|
|
'icmpv6.opt.src_linkaddr': _eth_addr,
|
|
'icmpv6.opt.target_linkaddr': _eth_addr,
|
|
'icmpv6.opt.route_lifetime': _auto,
|
|
'icmpv6.opt.route_info.flag.route_preference': _auto,
|
|
'icmpv6.opt.route_info.flag.reserved': _auto,
|
|
'icmpv6.opt.prefix': _list(_ipv6_addr),
|
|
'icmpv6.opt.length': _list(_auto),
|
|
'icmpv6.opt.reserved': _str,
|
|
'icmpv6.nd.ra.router_lifetime': _auto,
|
|
'icmpv6.nd.ra.retrans_timer': _auto,
|
|
'icmpv6.nd.ra.reachable_time': _auto,
|
|
'icmpv6.nd.ra.flag.rsv': _auto,
|
|
'icmpv6.nd.ra.flag.prf': _auto,
|
|
'icmpv6.nd.ra.flag.p': _auto,
|
|
'icmpv6.nd.ra.flag.o': _auto,
|
|
'icmpv6.nd.ra.flag.m': _auto,
|
|
'icmpv6.nd.ra.flag.h': _auto,
|
|
'icmpv6.echo.sequence_number': _auto,
|
|
'icmpv6.echo.identifier': _auto,
|
|
'icmpv6.data.len': _auto,
|
|
|
|
# COAP
|
|
'coap.code': _auto,
|
|
'coap.version': _auto,
|
|
'coap.type': _auto,
|
|
'coap.mid': _auto,
|
|
'coap.token_len': _auto,
|
|
'coap.token': _auto,
|
|
'coap.opt.uri_path': _list(_str),
|
|
'coap.opt.name': _list(_str),
|
|
'coap.opt.length': _list(_auto),
|
|
'coap.opt.uri_path_recon': _str,
|
|
'coap.payload': _payload,
|
|
'coap.payload_length': _auto,
|
|
'coap.payload_desc': _str,
|
|
'coap.opt.end_marker': _auto,
|
|
'coap.opt.desc': _list(_str),
|
|
'coap.opt.delta': _list(_auto),
|
|
'coap.response_to': _auto,
|
|
'coap.response_time': _float,
|
|
# COAP TLVS
|
|
'coap.tlv.type': _list(_auto),
|
|
'coap.tlv.status': _auto,
|
|
'coap.tlv.target_eid': _ipv6_addr,
|
|
'coap.tlv.ml_eid': _ext_addr,
|
|
'coap.tlv.last_transaction_time': _auto,
|
|
'coap.tlv.rloc16': _auto,
|
|
'coap.tlv.net_name': _str,
|
|
'coap.tlv.ext_mac_addr': _ext_addr,
|
|
'coap.tlv.router_mask_assigned': _auto,
|
|
'coap.tlv.router_mask_id_seq': _auto,
|
|
|
|
# dtls
|
|
'dtls.handshake.type': _list(_auto),
|
|
'dtls.handshake.cookie': _auto,
|
|
'dtls.record.content_type': _list(_auto),
|
|
'dtls.alert_message.desc': _auto,
|
|
|
|
# thread beacon
|
|
'thread_bcn.protocol': _auto,
|
|
'thread_bcn.version': _auto,
|
|
'thread_bcn.network_name': _str,
|
|
'thread_bcn.epid': _ext_addr,
|
|
|
|
# thread_address
|
|
'thread_address.tlv.len': _list(_auto),
|
|
'thread_address.tlv.type': _list(_auto),
|
|
'thread_address.tlv.status': _auto,
|
|
'thread_address.tlv.target_eid': _ipv6_addr,
|
|
'thread_address.tlv.ext_mac_addr': _ext_addr,
|
|
'thread_address.tlv.router_mask_id_seq': _auto,
|
|
'thread_address.tlv.router_mask_assigned': _bytes,
|
|
'thread_address.tlv.rloc16': _hex,
|
|
'thread_address.tlv.target_eid': _ipv6_addr,
|
|
'thread_address.tlv.ml_eid': _ext_addr,
|
|
|
|
# thread bl
|
|
'thread_bl.tlv.type': _list(_auto),
|
|
'thread_bl.tlv.len': _list(_auto),
|
|
'thread_bl.tlv.target_eid': _ipv6_addr,
|
|
'thread_bl.tlv.ml_eid': _ext_addr,
|
|
'thread_bl.tlv.last_transaction_time': _auto,
|
|
'thread_bl.tlv.timeout': _auto,
|
|
# THEAD NM
|
|
'thread_nm.tlv.type': _list(_auto),
|
|
'thread_nm.tlv.ml_eid': _ext_addr,
|
|
'thread_nm.tlv.target_eid': _ipv6_addr,
|
|
'thread_nm.tlv.status': _auto,
|
|
'thread_nm.tlv.timeout': _auto,
|
|
# thread_meshcop is not a real layer
|
|
'thread_meshcop.len_size_mismatch': _str,
|
|
'thread_meshcop.tlv.type': _list(_auto),
|
|
'thread_meshcop.tlv.len8': _list(_auto),
|
|
'thread_meshcop.tlv.net_name': _list(_str), # from thread_bl
|
|
'thread_meshcop.tlv.commissioner_id': _str,
|
|
'thread_meshcop.tlv.commissioner_sess_id': _auto, # from mle
|
|
"thread_meshcop.tlv.channel_page": _auto, # from ble
|
|
"thread_meshcop.tlv.channel": _list(_auto), # from ble
|
|
"thread_meshcop.tlv.chan_mask": _str, # from ble
|
|
'thread_meshcop.tlv.chan_mask_page': _auto,
|
|
'thread_meshcop.tlv.chan_mask_len': _auto,
|
|
'thread_meshcop.tlv.chan_mask_mask': _bytes,
|
|
'thread_meshcop.tlv.discovery_req_ver': _auto,
|
|
'thread_meshcop.tlv.discovery_rsp_ver': _auto,
|
|
'thread_meshcop.tlv.discovery_rsp_n': _auto,
|
|
'thread_meshcop.tlv.energy_list': _list(_auto),
|
|
'thread_meshcop.tlv.pan_id': _list(_auto),
|
|
'thread_meshcop.tlv.xpan_id': _bytes,
|
|
'thread_meshcop.tlv.ml_prefix': _bytes,
|
|
'thread_meshcop.tlv.master_key': _bytes,
|
|
'thread_meshcop.tlv.pskc': _bytes,
|
|
'thread_meshcop.tlv.sec_policy_rot': _auto,
|
|
'thread_meshcop.tlv.sec_policy_o': _auto,
|
|
'thread_meshcop.tlv.sec_policy_n': _auto,
|
|
'thread_meshcop.tlv.sec_policy_r': _auto,
|
|
'thread_meshcop.tlv.sec_policy_c': _auto,
|
|
'thread_meshcop.tlv.sec_policy_b': _auto,
|
|
'thread_meshcop.tlv.state': _auto,
|
|
'thread_meshcop.tlv.steering_data': _bytes,
|
|
'thread_meshcop.tlv.unknown': _bytes,
|
|
'thread_meshcop.tlv.udp_port': _list(_auto),
|
|
'thread_meshcop.tlv.ba_locator': _auto,
|
|
'thread_meshcop.tlv.jr_locator': _auto,
|
|
'thread_meshcop.tlv.active_tstamp': _auto,
|
|
'thread_meshcop.tlv.pending_tstamp': _auto,
|
|
'thread_meshcop.tlv.delay_timer': _auto,
|
|
'thread_meshcop.tlv.ipv6_addr': _list(_ipv6_addr),
|
|
|
|
# THREAD NWD
|
|
'thread_nwd.tlv.type': _list(_auto),
|
|
'thread_nwd.tlv.len': _list(_auto),
|
|
'thread_nwd.tlv.stable': _list(_auto),
|
|
'thread_nwd.tlv.service.t': _auto,
|
|
'thread_nwd.tlv.service.s_id': _auto,
|
|
'thread_nwd.tlv.service.s_data_len': _auto,
|
|
'thread_nwd.tlv.service.s_data.seqno': _auto,
|
|
'thread_nwd.tlv.service.s_data.rrdelay': _auto,
|
|
'thread_nwd.tlv.service.s_data.mlrtimeout': _auto,
|
|
'thread_nwd.tlv.server_16': _list(_auto),
|
|
'thread_nwd.tlv.border_router_16': _list(_auto),
|
|
'thread_nwd.tlv.sub_tlvs': _list(_str),
|
|
# TODO: support thread_nwd.tlv.prefix.length and thread_nwd.tlv.prefix.domain_id
|
|
'thread_nwd.tlv.prefix': _list(_ipv6_addr),
|
|
'thread_nwd.tlv.border_router.pref': _auto,
|
|
'thread_nwd.tlv.border_router.flag.s': _list(_auto),
|
|
'thread_nwd.tlv.border_router.flag.r': _list(_auto),
|
|
'thread_nwd.tlv.border_router.flag.p': _list(_auto),
|
|
'thread_nwd.tlv.border_router.flag.o': _list(_auto),
|
|
'thread_nwd.tlv.border_router.flag.n': _list(_auto),
|
|
'thread_nwd.tlv.border_router.flag.dp': _list(_auto),
|
|
'thread_nwd.tlv.border_router.flag.d': _list(_auto),
|
|
'thread_nwd.tlv.border_router.flag.c': _list(_auto),
|
|
'thread_nwd.tlv.6co.flag.reserved': _auto,
|
|
'thread_nwd.tlv.6co.flag.cid': _auto,
|
|
'thread_nwd.tlv.6co.flag.c': _list(_auto),
|
|
'thread_nwd.tlv.6co.context_length': _auto,
|
|
|
|
# Thread Diagnostic
|
|
'thread_diagnostic.tlv.type': _list(_auto),
|
|
'thread_diagnostic.tlv.len8': _list(_auto),
|
|
'thread_diagnostic.tlv.general': _list(_str),
|
|
|
|
# DNS
|
|
'dns.resp.ttl': _auto,
|
|
'dns.flags.response': _auto,
|
|
}
|
|
|
|
_layer_containers = set()
|
|
|
|
for key in _LAYER_FIELDS:
|
|
assert key.strip() == key and ' ' not in key, key
|
|
secs = key.split('.')
|
|
assert len(secs) >= 2
|
|
assert secs[0] in VALID_LAYER_NAMES, secs[0]
|
|
for i in range(len(secs) - 2):
|
|
path = secs[0] + '.' + '.'.join(secs[1:i + 2])
|
|
assert path not in _LAYER_FIELDS, '%s can not be both field and path' % path
|
|
_layer_containers.add(path)
|
|
|
|
|
|
def is_layer_field(uri: str) -> bool:
|
|
"""
|
|
Returns if the URI is a valid layer field.
|
|
|
|
:param uri: The layer field URI.
|
|
"""
|
|
return uri in _LAYER_FIELDS
|
|
|
|
|
|
def is_layer_field_container(uri: str) -> bool:
|
|
"""
|
|
Returns if the URI is a valid layer field container.
|
|
|
|
:param uri: The layer field container URI.
|
|
"""
|
|
return uri in _layer_containers
|
|
|
|
|
|
def get_layer_field(packet: RawPacket, field_uri: str) -> Any:
|
|
"""
|
|
Get a given layer field from the packet.
|
|
|
|
:param packet: The packet.
|
|
:param field_uri: The layer field URI.
|
|
|
|
:return: The specified layer field.
|
|
"""
|
|
assert isinstance(packet, RawPacket)
|
|
secs = field_uri.split('.')
|
|
layer_depth = 0
|
|
layer_name = secs[0]
|
|
if layer_name.endswith('inner'):
|
|
layer_name = layer_name[:-len('inner')]
|
|
field_uri = '.'.join([layer_name] + secs[1:])
|
|
layer_depth = 1
|
|
|
|
if is_layer_field(field_uri):
|
|
candidate_layers = _get_candidate_layers(packet, layer_name)
|
|
for layers in candidate_layers:
|
|
if layer_depth >= len(layers):
|
|
continue
|
|
layer = layers[layer_depth]
|
|
v = layer.get_field(field_uri)
|
|
if v is not None:
|
|
try:
|
|
v = _LAYER_FIELDS[field_uri](v)
|
|
print("[%s = %r] " % (field_uri, v), file=sys.stderr)
|
|
return v
|
|
except Exception as ex:
|
|
raise ValueError('can not parse field %s = %r' % (field_uri,
|
|
(v.get_default_value(), v.raw_value))) from ex
|
|
|
|
print("[%s = %s] " % (field_uri, "null"), file=sys.stderr)
|
|
return nullField
|
|
|
|
elif is_layer_field_container(field_uri):
|
|
from pktverify.layer_fields_container import LayerFieldsContainer
|
|
return LayerFieldsContainer(packet, field_uri)
|
|
else:
|
|
raise NotImplementedError('Field %s is not valid, please add it to `_LAYER_FIELDS`' % field_uri)
|
|
|
|
|
|
def check_layer_field_exists(packet, field_uri):
|
|
"""
|
|
Check if a given layer field URI exists in the packet.
|
|
|
|
:param packet: The packet to check.
|
|
:param field_uri: The layer field URI.
|
|
:return: Whether the layer field URI exists in the packet.
|
|
"""
|
|
assert isinstance(packet, RawPacket)
|
|
secs = field_uri.split('.')
|
|
layer_name = secs[0]
|
|
|
|
if not is_layer_field(field_uri) and not is_layer_field_container(field_uri):
|
|
raise NotImplementedError('%s is neither a field or field container' % field_uri)
|
|
|
|
candidate_layers = _get_candidate_layers(packet, layer_name)
|
|
for layers in candidate_layers:
|
|
for layer in layers:
|
|
for k, v in layer._all_fields.items():
|
|
if k == field_uri or k.startswith(field_uri + '.'):
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def _get_candidate_layers(packet, layer_name):
|
|
if layer_name == 'thread_meshcop':
|
|
candidate_layer_names = ['thread_meshcop', 'mle', 'coap', 'thread_bl', 'thread_nm']
|
|
elif layer_name == 'thread_nwd':
|
|
candidate_layer_names = ['mle', 'thread_address', 'thread_diagnostic']
|
|
elif layer_name == 'wpan':
|
|
candidate_layer_names = ['wpan', 'mle']
|
|
elif layer_name == 'ip':
|
|
candidate_layer_names = ['ip', 'ipv6']
|
|
elif layer_name == 'thread_bcn':
|
|
candidate_layer_names = ['thread_bcn']
|
|
else:
|
|
candidate_layer_names = [layer_name]
|
|
|
|
layers = []
|
|
for ln in candidate_layer_names:
|
|
if hasattr(packet, ln):
|
|
layers.append(packet.get_multiple_layers(ln))
|
|
|
|
return layers
|