1#!/usr/bin/env python3
2#
3#  Copyright (c) 2019, The OpenThread Authors.
4#  All rights reserved.
5#
6#  Redistribution and use in source and binary forms, with or without
7#  modification, are permitted provided that the following conditions are met:
8#  1. Redistributions of source code must retain the above copyright
9#     notice, this list of conditions and the following disclaimer.
10#  2. Redistributions in binary form must reproduce the above copyright
11#     notice, this list of conditions and the following disclaimer in the
12#     documentation and/or other materials provided with the distribution.
13#  3. Neither the name of the copyright holder nor the
14#     names of its contributors may be used to endorse or promote products
15#     derived from this software without specific prior written permission.
16#
17#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20#  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21#  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22#  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23#  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24#  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25#  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26#  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27#  POSSIBILITY OF SUCH DAMAGE.
28#
29import logging
30from typing import Optional
31
32from pyshark.packet.fields import LayerField, LayerFieldsContainer
33from pyshark.packet.layer import Layer as RawLayer
34from pyshark.packet.packet import Packet as RawPacket
35
36from pktverify.layer_fields import get_layer_field, check_layer_field_exists
37
38
39class Layer(object):
40    """
41    Represents a layer of a packet.
42    """
43
44    def __init__(self, packet: RawPacket, layer_name: str):
45        assert isinstance(packet, RawPacket)
46        assert isinstance(layer_name, str)
47        self._packet = packet
48        self._layer_name = layer_name
49
50    @property
51    def _layer(self) -> Optional[RawLayer]:
52        try:
53            return getattr(self._packet, self._layer_name)
54        except AttributeError:
55            return None
56
57    @property
58    def layer_name(self) -> str:
59        """
60        Returns the layer name.
61        """
62        return self._layer_name
63
64    def show(self):
65        """
66        Print the layer information.
67        """
68        print(self._layer)
69
70    def has(self, name) -> bool:
71        """
72        Returns if the layer has a given field.
73
74        :param name: The field name.
75        """
76        path = '%s.%s' % (self.layer_name, name)
77        return check_layer_field_exists(self._packet, path)
78
79    def __bool__(self):
80        """
81        Returns if this layer exists in the packet.
82        """
83        layer_exists = hasattr(self._packet, self._layer_name)
84        return layer_exists
85
86    def __getattr__(self, name):
87        """
88        Returns the layer field or container of a given field name.
89
90        :param name: The name of layer field or container.
91        """
92        path = '%s.%s' % (self.layer_name, name)
93        v = get_layer_field(self._packet, path)
94        assert not isinstance(v, (LayerField, LayerFieldsContainer)), '%s = %s(%r)' % (path, v.__class__.__name__, v)
95        setattr(self, name, v)
96        return v
97
98    def _add_field(self, key: str, val: str):
99        logging.debug("layer %s add field: %s = %s", self.layer_name, key, val)
100        field = LayerField(name=key, value=val)
101        all_fields = self._layer._all_fields
102        if key not in all_fields:
103            all_fields[key] = LayerFieldsContainer(main_field=field)
104        else:
105            all_fields[key].fields.append(field)
106
107
108class ThreadMeshcopLayer(Layer):
109    """
110    Represents the Thread MeshCop layer of a packet.
111    """
112
113    def __bool__(self):
114        raise NotImplementedError("thread_meshcop is not a real layer, please do not check as bool")
115
116
117class ThreadNetworkDataLayer(Layer):
118    """
119    Represents the Thread NetworkData layer of a packet.
120    """
121
122    def __bool__(self):
123        raise NotImplementedError("thread_nwd is not a real layer, please do not check as bool")
124
125
126class Icmpv6Layer(Layer):
127    """
128    Represents the ICMPv6 layer of a packet.
129    """
130
131    @property
132    def is_ping(self) -> bool:
133        """
134        Returns if the ICMPv6 layer is a Ping Request or Reply.
135        """
136        return self.type in (128, 129)
137
138    @property
139    def is_ping_request(self) -> bool:
140        """
141        Returns if the ICMPv6 layer is a Ping Request.
142        """
143        return self.type == 128
144
145    @property
146    def is_ping_reply(self) -> bool:
147        """
148        Returns if the ICMPv6 layer is a Ping Reply.
149        """
150        return self.type == 129
151
152    @property
153    def is_neighbor_advertisement(self) -> bool:
154        """
155        Returns if the ICMPv6 layer is a Neighbor Advertisement.
156        """
157        return self.type == 136
158
159    @property
160    def is_neighbor_solicitation(self) -> bool:
161        """
162        Returns if the ICMPv6 layer is a Neighbor Solicitation.
163        """
164        return self.type == 135
165
166    @property
167    def is_router_advertisement(self) -> bool:
168        """
169        Returns if the ICMPv6 layer is a Router Advertisement.
170        """
171        return self.type == 134
172
173
174class WpanLayer(Layer):
175    """
176    Represents the WPAN layer of a packet.
177    """
178
179    @property
180    def is_ack(self) -> bool:
181        return self.frame_type == 0x2
182