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 ipaddress
30from typing import Union
31
32from pktverify.bytes import Bytes
33
34
35class EthAddr(Bytes):
36    """
37    Represents an Ethernet address.
38    """
39
40    def __init__(self, addr: Union[str, bytearray, 'Bytes']):
41        super().__init__(addr)
42        if len(self) not in (6, 8):
43            raise ValueError((addr, self))
44
45    def __repr__(self):
46        return '%s(%r)' % (self.__class__.__name__, self.format_octets())
47
48    def __str__(self):
49        return self.format_octets()
50
51
52class ExtAddr(Bytes):
53    """
54    Represents an WPAN Extended address.
55    """
56
57    def __init__(self, addr: Union[str, bytearray, 'Bytes']):
58        super().__init__(addr)
59        if len(self) != 8:
60            raise ValueError((addr, self))
61
62    def __repr__(self):
63        return '%s(%r)' % (self.__class__.__name__, self.format_octets())
64
65    def __str__(self):
66        return self.format_octets()
67
68
69class Ipv6Addr(Bytes):
70    """
71    Represents an Ip6 address.
72    """
73
74    def __init__(self, addr: Union[str, bytearray, 'Bytes']):
75        if isinstance(addr, str):
76            # try to parse compacted ipv6 address
77            try:
78                addr = Ipv6Addr._expand(addr)
79            except ipaddress.AddressValueError:
80                pass
81
82        super().__init__(addr)
83        if len(self) != 16:
84            raise ValueError(addr)
85        self._addr = ipaddress.IPv6Address(self)
86
87    def __repr__(self):
88        return '%s(%r)' % (self.__class__.__name__, self.format_hextets())
89
90    def __str__(self):
91        return self.format_hextets()
92
93    @staticmethod
94    def _expand(addr) -> str:
95        assert isinstance(addr, str)
96        a = ipaddress.IPv6Address(addr)
97        return a.exploded
98
99    @property
100    def is_global(self) -> bool:
101        """
102        Returns if the Ip6 address is global.
103        """
104        if self._addr.is_global:
105            return True
106
107        if self._addr.is_link_local or self._addr.is_multicast or self._addr.is_loopback:
108            return False
109
110        return True
111
112    @property
113    def is_dua(self) -> bool:
114        """
115        Returns if the Ip6 address is Domain Unicast Address.
116        """
117        from pktverify import consts
118        return self.startswith(consts.DOMAIN_PREFIX)
119
120    @property
121    def is_backbone_gua(self) -> bool:
122        """
123        Returns if the Ip6 address is Backbone address.
124        """
125        from pktverify import consts
126        return self.startswith(consts.BACKBONE_IPV6_PREFIX)
127
128    @property
129    def is_link_local(self) -> bool:
130        """
131        Returns if the Ip6 address is link local.
132        """
133        return self._addr.is_link_local
134
135    @property
136    def is_multicast(self) -> bool:
137        """
138        Returns if the Ip6 address is multicast.
139        """
140        return self._addr.is_multicast
141
142    @property
143    def is_mleid(self) -> bool:
144        """
145        Returns if the Ip6 address is ML-EID.
146        """
147        from pktverify.consts import DEFAULT_MESH_LOCAL_PREFIX
148        return self.startswith(DEFAULT_MESH_LOCAL_PREFIX)
149
150
151if __name__ == '__main__':
152    a = EthAddr("010203040506")
153    assert a == EthAddr("01:02:03:04:05:06")
154    assert a == EthAddr("0102:0304:0506")
155    assert eval(repr(a)) == a
156    assert a == str(a)
157    assert str(a) == a
158
159    print(a, repr(a))
160    assert isinstance(a[:], Bytes)
161    assert a[:3] == "010203"
162    assert a[3:] == "040506"
163
164    a = ExtAddr("0102030405060708")
165    assert a == ExtAddr("01:02:03:04:05:06:07:08")
166    assert a == ExtAddr("0102:0304:0506:0708")
167    assert eval(repr(a)) == a
168    assert a == str(a)
169    assert str(a) == a
170
171    print(a, repr(a))
172    assert isinstance(a[:], Bytes)
173    assert a[:4] == "01020304"
174    assert a[4:] == "05060708"
175
176    a = Ipv6Addr("00112233445566778899aabbccddeeff")
177    assert a == Ipv6Addr("00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff")
178    assert a == Ipv6Addr("0011:2233:4455:6677:8899:aabb:ccdd:eeff")
179    assert eval(repr(a)) == a
180    assert a == str(a)
181    assert str(a) == a
182
183    print(a, repr(a))
184    assert isinstance(a[:], Bytes)
185    assert a[:4] == "00112233"
186    assert a[-4:] == "ccddeeff"
187
188    assert Ipv6Addr("fd00:db8::ff:fe00:8001") == Ipv6Addr("fd00:0db8:0000:0000:0000:00ff:fe00:8001")
189    print(Ipv6Addr("fdde:ad00:beef:0:9d87:85f0:3358:3fff"))
190