1#!/usr/bin/env python3
2#
3#  Copyright (c) 2020, 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 collections import namedtuple
31from enum import IntEnum
32
33
34class ChildId(int):
35    """Represents a Child ID."""
36    pass
37
38
39class RouterId(int):
40    """Represents a Router ID."""
41    pass
42
43
44class Rloc16(int):
45    """Represents a RLOC16."""
46
47    def __repr__(self):
48        return '0x%04x' % self
49
50
51class PartitionId(int):
52    """Represents a Thread Network Partition ID."""
53    pass
54
55
56class NetifIdentifier(IntEnum):
57    """Represents a network interface identifier."""
58    UNSPECIFIED = 0
59    THERAD = 1
60    BACKBONE = 2
61
62
63class DeviceMode(str):
64    """Represents a device mode."""
65
66    def __new__(cls, o: str):
67        ins = str.__new__(cls, o)
68
69        if ins != '-':
70            for c in ins:
71                if c not in 'rdn':
72                    raise ValueError(o)
73
74        # check for empty mode (SED should use "-")
75        if not ins:
76            raise ValueError(o)
77
78        # check for duplicate chars
79        if len(ins) != len(set(ins)):
80            raise ValueError(o)
81
82        return ins
83
84
85class ThreadState(str):
86    """Represents a Thread state."""
87    _VALID_VALUES = {'disabled', 'detached', 'child', 'router', 'leader'}
88
89    def __new__(cls, o: str):
90        ins = str.__new__(cls, o)
91
92        if ins not in ThreadState._VALID_VALUES:
93            raise ValueError(o)
94
95        return ins
96
97
98class Ip6Addr(ipaddress.IPv6Address):
99    """Represents an IPv6 address."""
100
101    def __eq__(self, other):
102        if isinstance(other, str):
103            other = ipaddress.IPv6Address(other)
104
105        return super().__eq__(other)
106
107    def __repr__(self):
108        return self.compressed
109
110    def __hash__(self):
111        return super().__hash__()
112
113
114class Ip6Prefix(ipaddress.IPv6Network):
115    """Represents an IPv6 prefix."""
116
117    def __eq__(self, other):
118        if isinstance(other, str):
119            other = ipaddress.IPv6Network(other)
120
121        return super().__eq__(other)
122
123    def __repr__(self):
124        return self.compressed
125
126    def __hash__(self):
127        return super().__hash__()
128
129
130SecurityPolicy = namedtuple('SecurityPolicy', ['rotation_time', 'flags'])
131"""Represents a Security Policy configuration."""
132
133
134class RouterTableEntry(dict):
135
136    @property
137    def is_link_established(self):
138        return bool(self['link'])
139
140
141if __name__ == '__main__':
142    assert Ip6Addr('2001:0:0:0:0:0:0:1') == '2001::1'
143    assert repr(Ip6Addr('2001:0:0:0:0:0:0:1')) == '2001::1'
144    assert str(Ip6Addr('2001:0:0:0:0:0:0:1')) == '2001::1'
145    assert Ip6Prefix('2001:0:0:0::/64') == '2001::/64'
146    assert repr(Ip6Prefix('2001:0:0:0::/64')) == '2001::/64'
147    assert str(Ip6Prefix('2001:0:0:0::/64')) == '2001::/64'
148