1# 2# Copyright (c) 2019, The OpenThread Authors. 3# All rights reserved. 4# 5# Redistribution and use in source and binary forms, with or without 6# modification, are permitted provided that the following conditions are met: 7# 1. Redistributions of source code must retain the above copyright 8# notice, this list of conditions and the following disclaimer. 9# 2. Redistributions in binary form must reproduce the above copyright 10# notice, this list of conditions and the following disclaimer in the 11# documentation and/or other materials provided with the distribution. 12# 3. Neither the name of the copyright holder nor the 13# names of its contributors may be used to endorse or promote products 14# derived from this software without specific prior written permission. 15# 16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26# POSSIBILITY OF SUCH DAMAGE. 27# 28 29import logging 30import sys 31from operator import attrgetter 32from typing import Optional, Callable, Tuple, Union 33 34from pktverify import consts, errors 35from pktverify.addrs import EthAddr, ExtAddr, Ipv6Addr 36from pktverify.bytes import Bytes 37from pktverify.consts import THREAD_ALLOWED_ICMPV6_TYPES 38from pktverify.packet import Packet 39from pktverify.utils import make_filter_func 40 41WPAN, ETH = 0, 1 42 43 44class _SavedIndex(object): 45 __slots__ = ('_pkts', '_saved_index') 46 47 def __init__(self, pkts): 48 self._pkts = pkts 49 self._saved_index = pkts.index 50 51 def __enter__(self): 52 pass 53 54 def __exit__(self, exc_type, exc_val, exc_tb): 55 self._pkts.index = self._saved_index 56 57 58def _always_true(p): 59 return True 60 61 62class PacketFilter(object): 63 """ 64 Represents a range of packets that are filtered by given filter 65 """ 66 67 def __init__(self, 68 pkts, 69 start=(0, 0), 70 stop=None, 71 *, 72 index=None, 73 filter_func: Optional[Callable] = None, 74 parent: Optional['PacketFilter'] = None): 75 if stop is None: 76 stop = (len(pkts), len(pkts)) 77 78 self._pkts = pkts 79 self._start_index = start 80 self._stop_index = stop 81 self._index = index if index is not None else self._start_index 82 self._last_index = -1 83 self._filter_func = filter_func or _always_true 84 self._parent = parent 85 self._check_type_ok() 86 87 def _check_type_ok(self): 88 assert self._last_index == -1 or 0 <= self._last_index < len(self._pkts) 89 90 assert isinstance(self._start_index, tuple) and len(self._start_index) == 2, self._start_index 91 assert isinstance(self._stop_index, tuple) and len(self._stop_index) == 2, self._stop_index 92 assert isinstance(self._index, tuple) and len(self._index) == 2, self._index 93 self._check_idx_range_ok((0, 0), self._start_index) 94 self._check_idx_range_ok(self._start_index, self._index) 95 self._check_idx_range_ok(self._index, self._stop_index) 96 self._check_idx_range_ok(self._stop_index, (len(self._pkts), len(self._pkts))) 97 98 def _check_idx_range_ok(self, start, stop): 99 assert start[0] <= stop[0], (start, stop) 100 assert start[1] <= stop[1], (start, stop) 101 102 @property 103 def index(self) -> Tuple[int, int]: 104 """ 105 :return: the current index (which is a tuple) 106 """ 107 return self._index 108 109 @index.setter 110 def index(self, index: Tuple[int, int]): 111 """ 112 Set the current index 113 :param index: the index tuple to set 114 """ 115 assert isinstance(index, tuple) and len(index) == 2, index 116 self._check_type_ok() 117 self._index = index 118 self._check_type_ok() 119 120 def __len__(self): 121 """ 122 :return: length of packets 123 """ 124 return len(self._pkts) 125 126 def save_index(self): 127 """ 128 Save the current index to be restored. 129 130 :return: a context that saves the current index when entering, and restores when exiting 131 """ 132 return _SavedIndex(self) 133 134 @property 135 def start_index(self) -> Tuple[int, int]: 136 """ 137 :return: the start index tuple 138 """ 139 return self._start_index 140 141 @property 142 def stop_index(self) -> Tuple[int, int]: 143 """ 144 :return: the stop index tuple 145 """ 146 return self._stop_index 147 148 def filter(self, func, cascade=True, **vars) -> 'PacketFilter': 149 """ 150 Create a new PacketFilter based on this packet filter with given filter func 151 152 :param func: a callable that returns a bool (e.x. lambda p: xxx) or a filter string 153 :param cascade: True if calling next in the new filter will also set index for this filter, False otherwise 154 :param vars: variables for filter string 155 :return: a new PacketFilter 156 """ 157 print('\n>>> filtering in range %s~%s%s:' % 158 (self._index, self._stop_index, "<end>" if self._stop_index == len(self._pkts) else "<stop>"), 159 file=sys.stderr) 160 161 func = make_filter_func(func, **vars) 162 self._check_type_ok() 163 return PacketFilter(self._pkts, 164 self._index, 165 self._stop_index, 166 filter_func=lambda p: self._filter_func(p) and func(p), 167 parent=self if cascade else None) 168 169 def filter_if(self, cond: bool, *args, **kwargs) -> 'PacketFilter': 170 """ 171 Create a filter using given arguments if `cond` is true. 172 173 :param cond: the condition to be checked 174 :param args: arguments for filter func 175 :param kwargs: arguments for filter func 176 :return: a sub filter using given arguments if cond is true, or self otherwise 177 """ 178 if cond: 179 return self.filter(*args, **kwargs) 180 else: 181 return self 182 183 @property 184 def last_index(self) -> Tuple[int, int]: 185 return self._last_index 186 187 def last(self) -> Packet: 188 """ 189 :return: the last packet found 190 """ 191 if self._last_index >= 0: 192 return self._pkts[self._last_index] 193 else: 194 raise errors.PacketNotFound(self.index, self._stop_index) 195 196 def next(self) -> Optional[Packet]: 197 """ 198 Find the next packet starting from the current index to the stop index that matches the current filter. 199 200 :return: the next matching packet, or None if packet not found 201 """ 202 self._check_type_ok() 203 idx = min(self._index) 204 stop_idx = max(self._stop_index) 205 206 while idx < stop_idx: 207 p = self._pkts[idx] 208 209 sys.stderr.write('#%d %s' % (idx + 1, '\n' if idx % 40 == 39 else '')) 210 if self._filter_func(p): 211 if p.wpan and not (self._index[0] <= idx < self._stop_index[0]): # wpan matched but not in range 212 pass 213 elif p.eth and not (self._index[1] <= idx < self._stop_index[1]): # eth matched but not in range 214 pass 215 else: 216 self._on_found_next(idx, p) 217 print("\n>>> found packet at #%d!" % (idx + 1,), file=sys.stderr) 218 return p 219 220 idx += 1 221 222 return None 223 224 def must_next(self) -> Packet: 225 """ 226 Call .next(), raise error if packet is not found. 227 228 :return: the next matching packet 229 """ 230 p = self.next() 231 if p is not None: 232 return p 233 else: 234 raise errors.PacketNotFound(self.index, self._stop_index) 235 236 def must_not_next(self) -> None: 237 """ 238 Call .next(), raise error if packet is found 239 """ 240 p = self.next() 241 if p is None: 242 return 243 else: 244 logging.error("Found unexpected packet at #%s", self.index) 245 p.show() 246 p.debug_fields() 247 raise errors.UnexpectedPacketFound(self.index, p) 248 249 def _on_found_next(self, idx: int, p: Packet): 250 assert self._pkts[idx] is p 251 assert idx >= min(self._index) 252 assert not p.wpan or idx >= self._index[0] 253 assert not p.eth or idx >= self._index[1], (self._index, idx) 254 255 if p.wpan: 256 wpan_idx = idx + 1 257 eth_idx = max(self._index[1], 258 self._find_prev_packet(idx + 1, p.sniff_timestamp - consts.AUTO_SEEK_BACK_MAX_DURATION, ETH)) 259 else: 260 eth_idx = idx + 1 261 wpan_idx = max( 262 self._index[0], 263 self._find_prev_packet(idx + 1, p.sniff_timestamp - consts.AUTO_SEEK_BACK_MAX_DURATION, WPAN)) 264 265 # make sure index never go back 266 assert wpan_idx >= self._index[0] 267 assert eth_idx >= self._index[1] 268 269 print('\n>>>_on_found_next %d %s => %s' % (idx, self._index, (wpan_idx, eth_idx)), file=sys.stderr) 270 self._set_found_index(idx, (wpan_idx, eth_idx)) 271 272 def _find_prev_packet(self, idx, min_sniff_timestamp, pkttype): 273 assert pkttype in (WPAN, ETH) 274 275 prev_idx = idx 276 while idx > 0 and self._pkts[idx - 1].sniff_timestamp >= min_sniff_timestamp: 277 idx -= 1 278 if pkttype == WPAN and self._pkts[idx].wpan: 279 prev_idx = idx 280 elif pkttype == ETH and self._pkts[idx].eth: 281 prev_idx = idx 282 283 return prev_idx 284 285 def __iter__(self): 286 for pkt in self._pkts: 287 yield pkt 288 289 def range(self, start, stop=None, cascade=True) -> 'PacketFilter': 290 """ 291 Create a new PacketFilter using the specified start and stop index tuples 292 293 :param start: the new start index tuple 294 :param stop: the new stop index tuple 295 :param cascade: True if calling next in the new filter will also set index for this filter, False otherwise 296 :return: a new PacketFilter with new start and stop range 297 """ 298 299 if stop is None: 300 stop = self._stop_index 301 302 assert self._start_index <= start <= self._stop_index 303 assert self._start_index <= stop <= self._stop_index 304 return PacketFilter(self._pkts, start, stop, filter_func=self._filter_func, parent=self if cascade else None) 305 306 def copy(self) -> 'PacketFilter': 307 """ 308 :return: a copy of the current PacketFilter 309 """ 310 return PacketFilter(self._pkts, self._index, self._stop_index, filter_func=self._filter_func, parent=None) 311 312 def __getitem__(self, index: int) -> Packet: 313 """ 314 :param index: the packet index (not tuple!) 315 :return: the packet at the specified index 316 """ 317 assert isinstance(index, int), index 318 return self._pkts[index] 319 320 def seek_back(self, max_duration: float, *, eth=False, wpan=False) -> 'PacketFilter': 321 """ 322 Move the current index back in time within the specified max duration. Either eth or wpan must be True. 323 324 :param max_duration: the max duration to move back 325 :param eth: True if eth index can be moved back 326 :param wpan: True if wpan index can be moved back 327 :return: self 328 """ 329 assert eth or wpan, "must have eth or wpan" 330 331 wpan_idx = self._index[0] 332 if wpan and wpan_idx < len(self._pkts): 333 wpan_idx = self._find_prev_packet(wpan_idx, self._pkts[wpan_idx].sniff_timestamp - max_duration, WPAN) 334 wpan_idx = max(self._start_index[0], wpan_idx) 335 336 eth_idx = self._index[1] 337 if eth and eth_idx < len(self._pkts): 338 eth_idx = self._find_prev_packet(eth_idx, self._pkts[eth_idx].sniff_timestamp - max_duration, ETH) 339 eth_idx = max(self._start_index[1], eth_idx) 340 341 print("\n>>> back %s wpan=%s, eth=%s: index %s => %s" % (max_duration, wpan, eth, self._index, 342 (wpan_idx, eth_idx)), 343 file=sys.stderr) 344 self._index = (wpan_idx, eth_idx) 345 self._check_type_ok() 346 return self 347 348 def _set_found_index(self, last_index: Tuple[int, int], index: Tuple[int, int]): 349 self._last_index = last_index 350 self._index = index 351 self._check_type_ok() 352 353 if self._parent is not None: 354 self._parent._set_found_index(last_index, index) 355 356 def filter_mle_advertisement(self, role: str, **kwargs): 357 assert role in ('Leader', 'Router', 'REED'), role 358 359 tlv_set = {consts.LEADER_DATA_TLV, consts.SOURCE_ADDRESS_TLV} 360 361 if role != 'REED': 362 tlv_set.add(consts.ROUTE64_TLV) 363 364 return self.filter_LLANMA(). \ 365 filter_mle_cmd(consts.MLE_ADVERTISEMENT). \ 366 filter(lambda p: tlv_set == 367 set(p.mle.tlv.type) and \ 368 p.ipv6.hlim == 255, **kwargs 369 ) 370 371 def filter_coap(self, **kwargs): 372 """ 373 Create a new PacketFilter to filter COAP packets. 374 375 :param kwargs: Extra arguments for `filter`. 376 :return: The new PacketFilter to filter COAP packets. 377 """ 378 return self.filter(attrgetter('coap'), **kwargs) 379 380 def filter_coap_request(self, uri_path, port=None, confirmable=None, **kwargs): 381 """ 382 Create a new PacketFilter to filter COAP Request packets. 383 384 :param uri_path: The COAP URI path to filter. 385 :param port: The UDP port to filter if specified. 386 :param kwargs: Extra arguments for `filter`. 387 :return: The new PacketFilter to filter COAP Request packets. 388 """ 389 assert isinstance(uri_path, str), uri_path 390 assert port is None or isinstance(port, int), port 391 return self.filter( 392 lambda p: (p.coap.is_post and p.coap.opt.uri_path_recon == uri_path and 393 (confirmable is None or p.coap.type == 394 (0 if confirmable else 1)) and (port is None or p.udp.dstport == port)), **kwargs) 395 396 def filter_coap_ack(self, uri_path, port=None, **kwargs): 397 """ 398 Create a new PacketFilter for filter COAP ACK packets. 399 400 :param uri_path: The COAP URI path to filter. 401 :param port: The UDP port to filter if specified. 402 :param kwargs: Extra arguments for `filter`. 403 :return: The new PacketFilter to filter COAP ACK packets. 404 """ 405 assert isinstance(uri_path, str), uri_path 406 assert port is None or isinstance(port, int), port 407 return self.filter( 408 lambda p: (p.coap.is_ack and p.coap.opt.uri_path_recon == uri_path and 409 (port is None or p.udp.dstport == port)), **kwargs) 410 411 def filter_backbone_answer(self, 412 target: str, 413 *, 414 eth_src: Optional[EthAddr] = None, 415 port: int = None, 416 confirmable: bool = None, 417 mliid=None): 418 filter_eth = self.filter_eth_src(eth_src) if eth_src else self.filter_eth() 419 f = filter_eth.filter_coap_request('/b/ba', port=port, 420 confirmable=confirmable).filter('thread_bl.tlv.target_eid == {target}', 421 target=target) 422 if mliid is not None: 423 f = f.filter('thread_bl.tlv.ml_eid == {mliid}', mliid=mliid) 424 425 return f 426 427 def filter_backbone_query(self, target: str, *, eth_src: EthAddr, port: int = None) -> 'PacketFilter': 428 return self.filter_eth_src(eth_src).filter_coap_request('/b/bq', port=port, confirmable=False).filter( 429 'thread_bl.tlv.target_eid == {target}', target=target) 430 431 def filter_wpan(self, **kwargs): 432 """ 433 Create a new PacketFilter for filter WPAN packets. 434 435 :param kwargs: Extra arguments for `filter`. 436 :return: The new PacketFilter to filter WPAN packets. 437 """ 438 return self.filter(attrgetter('wpan'), **kwargs) 439 440 def filter_wpan_ack(self, **kwargs): 441 """ 442 Create a new PacketFilter for filter WPAN ACK packets. 443 444 :param kwargs: Extra arguments for `filter`. 445 :return: The new PacketFilter to filter WPAN packets. 446 """ 447 return self.filter(lambda p: p.wpan.frame_type == consts.MAC_FRAME_TYPE_ACK, **kwargs) 448 449 def filter_wpan_beacon(self, **kwargs): 450 """ 451 Create a new PacketFilter for filter WPAN beacon. 452 453 :param kwargs: Extra arguments for `filter`. 454 :return: The new PacketFilter to filter WPAN packets. 455 """ 456 return self.filter(lambda p: p.wpan.frame_type == consts.MAC_FRAME_TYPE_BEACON, **kwargs) 457 458 def filter_wpan_data(self, **kwargs): 459 """ 460 Create a new PacketFilter for filter WPAN data packets. 461 462 :param kwargs: Extra arguments for `filter`. 463 :return: The new PacketFilter to filter WPAN packets. 464 """ 465 return self.filter(lambda p: p.wpan.frame_type == consts.MAC_FRAME_TYPE_DATA, **kwargs) 466 467 def filter_wpan_seq(self, seq, **kwargs): 468 """ 469 Create a new PacketFilter for filter WPAN packets of a sequence number. 470 471 :param seq: The sequence number to filter. 472 :param kwargs: Extra arguments for `filter`. 473 :return: The new PacketFilter to filter WPAN packets. 474 """ 475 return self.filter(lambda p: p.wpan.seq_no == seq, **kwargs) 476 477 def filter_wpan_version(self, version: int, **kwargs): 478 """ 479 Create a new PacketFilter for filter WPAN packets of a given version. 480 481 :param version: The version to filter. 482 :param kwargs: Extra arguments for `filter`. 483 :return: The new PacketFilter to filter WPAN packets. 484 """ 485 return self.filter(lambda p: p.wpan.version == version, **kwargs) 486 487 def filter_wpan_channel(self, channel: int, **kwargs): 488 """ 489 Create a new PacketFilter for filter WPAN packets of a given channel. 490 491 :param channel: The channel to filter. 492 :param kwargs: Extra arguments for `filter`. 493 :return: The new PacketFilter to filter WPAN packets. 494 """ 495 return self.filter(lambda p: p.wpan.channel == channel, **kwargs) 496 497 def filter_wpan_src16(self, addr, **kwargs): 498 return self.filter(lambda p: p.wpan.src16 == addr, **kwargs) 499 500 def filter_wpan_dst16(self, addr, **kwargs): 501 return self.filter(lambda p: p.wpan.dst16 == addr, **kwargs) 502 503 def filter_wpan_src16_dst16(self, src_addr, dst_addr, **kwargs): 504 return self.filter(lambda p: p.wpan.src16 == src_addr and p.wpan.dst16 == dst_addr, **kwargs) 505 506 def filter_wpan_src64(self, addr, **kwargs): 507 assert isinstance(addr, (str, ExtAddr)), addr 508 return self.filter(lambda p: p.wpan.src64 == addr, **kwargs) 509 510 def filter_wpan_dst64(self, addr, **kwargs): 511 assert isinstance(addr, (str, ExtAddr)), addr 512 return self.filter(lambda p: p.wpan.dst64 == addr, **kwargs) 513 514 def filter_dst16(self, rloc16: int, **kwargs): 515 return self.filter(lambda p: p.lowpan.mesh.dest16 == rloc16 or p.wpan.dst16 == rloc16, **kwargs) 516 517 def filter_wpan_ie_present(self, **kwargs): 518 return self.filter(lambda p: p.wpan.ie_present == 1) 519 520 def filter_wpan_ie_not_present(self, **kwargs): 521 return self.filter(lambda p: p.wpan.ie_present == 0) 522 523 def filter_ping_request(self, identifier=None, **kwargs): 524 return self.filter( 525 lambda p: p.icmpv6.is_ping_request and (identifier is None or p.icmpv6.echo.identifier == identifier), 526 **kwargs) 527 528 def filter_ping_reply(self, **kwargs): 529 identifier = kwargs.pop('identifier', None) 530 return self.filter( 531 lambda p: (p.icmpv6.is_ping_reply and (identifier is None or p.icmpv6.echo.identifier == identifier)), 532 **kwargs) 533 534 def filter_eth(self, **kwargs): 535 return self.filter(attrgetter('eth'), **kwargs) 536 537 def filter_eth_src(self, addr, **kwargs): 538 assert isinstance(addr, (str, EthAddr)) 539 return self.filter(lambda p: p.eth.src == addr, **kwargs) 540 541 def filter_ipv6_dst(self, addr, **kwargs): 542 assert isinstance(addr, (str, Ipv6Addr)) 543 return self.filter(lambda p: p.ipv6.dst == addr, **kwargs) 544 545 def filter_ipv6_2dsts(self, addr1, addr2, **kwargs): 546 assert isinstance(addr1, (str, Ipv6Addr)) 547 assert isinstance(addr2, (str, Ipv6Addr)) 548 return self.filter(lambda p: p.ipv6.dst == addr1 or p.ipv6.dst == addr2, **kwargs) 549 550 def filter_ipv6_src_dst(self, src_addr, dst_addr, **kwargs): 551 assert isinstance(src_addr, (str, Ipv6Addr)) 552 assert isinstance(dst_addr, (str, Ipv6Addr)) 553 return self.filter(lambda p: p.ipv6.src == src_addr and p.ipv6.dst == dst_addr, **kwargs) 554 555 def filter_LLATNMA(self, **kwargs): 556 return self.filter(lambda p: p.ipv6.dst == consts.LINK_LOCAL_All_THREAD_NODES_MULTICAST_ADDRESS, **kwargs) 557 558 def filter_RLANMA(self, **kwargs): 559 return self.filter(lambda p: p.ipv6.dst == consts.REALM_LOCAL_ALL_NODES_ADDRESS, **kwargs) 560 561 def filter_RLARMA(self, **kwargs): 562 return self.filter(lambda p: p.ipv6.dst == consts.REALM_LOCAL_ALL_ROUTERS_ADDRESS, **kwargs) 563 564 def filter_RLATNMA(self, **kwargs): 565 return self.filter(lambda p: p.ipv6.dst == consts.REALM_LOCAL_All_THREAD_NODES_MULTICAST_ADDRESS, **kwargs) 566 567 def filter_LLANMA(self, **kwargs): 568 return self.filter(lambda p: p.ipv6.dst == consts.LINK_LOCAL_ALL_NODES_MULTICAST_ADDRESS, **kwargs) 569 570 def filter_LLABMA(self, **kwargs): 571 return self.filter(lambda p: p.ipv6.dst == consts.LINK_LOCAL_ALL_BBRS_MULTICAST_ADDRESS, **kwargs) 572 573 def filter_LLARMA(self, **kwargs): 574 return self.filter(lambda p: p.ipv6.dst == consts.LINK_LOCAL_ALL_ROUTERS_MULTICAST_ADDRESS, **kwargs) 575 576 def filter_AMPLFMA(self, mpl_seed_id: Union[int, Ipv6Addr] = None, **kwargs): 577 f = self.filter(lambda p: p.ipv6.dst == consts.ALL_MPL_FORWARDERS_MA, **kwargs) 578 if mpl_seed_id is not None: 579 if isinstance(mpl_seed_id, int): 580 mpl_seed_id = Bytes([mpl_seed_id >> 8, mpl_seed_id & 0xFF]) 581 f = f.filter(lambda p: p.ipv6.opt.mpl.seed_id == mpl_seed_id) 582 else: 583 rloc = mpl_seed_id 584 rloc16 = bytes(rloc[-2:]) 585 f = f.filter(lambda p: (p.ipv6.src == rloc and p.ipv6.opt.mpl.flag.s == 0) or 586 (p.ipv6.src != rloc and p.ipv6.opt.mpl.flag.s == 1 and p.ipv6.opt.mpl.seed_id == rloc16)) 587 return f 588 589 def filter_mle(self, **kwargs): 590 return self.filter(attrgetter('mle'), **kwargs) 591 592 def filter_wpan_cmd(self, cmd, **kwargs): 593 assert isinstance(cmd, int), cmd 594 return self.filter(lambda p: p.wpan.cmd == cmd, **kwargs) 595 596 def filter_mle_cmd(self, cmd, **kwargs): 597 assert isinstance(cmd, int), cmd 598 return self.filter(lambda p: p.mle.cmd == cmd, **kwargs) 599 600 def filter_mle_cmd2(self, cmd1, cmd2, **kwargs): 601 assert isinstance(cmd1, int), cmd1 602 assert isinstance(cmd2, int), cmd2 603 return self.filter(lambda p: p.mle.cmd == cmd1 or p.mle.cmd == cmd2, **kwargs) 604 605 def filter_mle_has_tlv(self, *tlv_types, **kwargs): 606 return self.filter(lambda p: set(tlv_types) <= set(p.mle.tlv.type), **kwargs) 607 608 def filter_icmpv6(self, **kwargs): 609 return self.filter(attrgetter('icmpv6'), **kwargs) 610 611 def filter_icmpv6_nd_ns(self, target_address: Ipv6Addr): 612 return self.filter(lambda p: 613 (p.icmpv6.is_neighbor_solicitation and p.icmpv6.nd.ns.target_address == target_address)) 614 615 def filter_icmpv6_nd_na(self, target_address: Ipv6Addr): 616 return self.filter(lambda p: 617 (p.icmpv6.is_neighbor_advertisement and p.icmpv6.nd.na.target_address == target_address)) 618 619 def filter_icmpv6_nd_ra(self): 620 return self.filter(lambda p: p.icmpv6.is_router_advertisement) 621 622 def filter_thread_unallowed_icmpv6(self): 623 return self.filter('wpan and icmpv6 and icmpv6.type not in {THREAD_ALLOWED_ICMPV6_TYPES}', 624 THREAD_ALLOWED_ICMPV6_TYPES=THREAD_ALLOWED_ICMPV6_TYPES) 625 626 def filter_has_bbr_dataset(self): 627 return self.filter(""" 628 thread_nwd.tlv.server.has('16') 629 and thread_nwd.tlv.service.s_data.seqno is not null 630 and thread_nwd.tlv.service.s_data.rrdelay is not null 631 and thread_nwd.tlv.service.s_data.mlrtimeout is not null 632 """) 633