1# -----------------------------------------------------------------------------
2# Copyright (c) 2008 by David P. D. Moss. All rights reserved.
3#
4# Released under the BSD license. See the LICENSE file for details.
5# -----------------------------------------------------------------------------
6"""
7IEEE 64-bit EUI (Extended Unique Identifier) logic.
8"""
9import struct as _struct
10import re as _re
11
12from netaddr.core import AddrFormatError
13from netaddr.strategy import (
14 valid_words as _valid_words,
15 int_to_words as _int_to_words,
16 words_to_int as _words_to_int,
17 valid_bits as _valid_bits,
18 bits_to_int as _bits_to_int,
19 int_to_bits as _int_to_bits,
20 valid_bin as _valid_bin,
21 int_to_bin as _int_to_bin,
22 bin_to_int as _bin_to_int,
23)
24
25
26#: The width (in bits) of this address type.
27width = 64
28
29#: The version of this address type.
30version = 64
31
32#: The maximum integer value that can be represented by this address type.
33max_int = 2**width - 1
34
35# -----------------------------------------------------------------------------
36# Dialect classes.
37# -----------------------------------------------------------------------------
38
39
40class eui64_base(object):
41 """A standard IEEE EUI-64 dialect class."""
42
43 #: The individual word size (in bits) of this address type.
44 word_size = 8
45
46 #: The number of words in this address type.
47 num_words = width // word_size
48
49 #: The maximum integer value for an individual word in this address type.
50 max_word = 2**word_size - 1
51
52 #: The separator character used between each word.
53 word_sep = '-'
54
55 #: The format string to be used when converting words to string values.
56 word_fmt = '%.2X'
57
58 #: The number base to be used when interpreting word values as integers.
59 word_base = 16
60
61
62class eui64_unix(eui64_base):
63 """A UNIX-style MAC address dialect class."""
64
65 word_size = 8
66 num_words = width // word_size
67 word_sep = ':'
68 word_fmt = '%x'
69 word_base = 16
70
71
72class eui64_unix_expanded(eui64_unix):
73 """A UNIX-style MAC address dialect class with leading zeroes."""
74
75 word_fmt = '%.2x'
76
77
78class eui64_cisco(eui64_base):
79 """A Cisco 'triple hextet' MAC address dialect class."""
80
81 word_size = 16
82 num_words = width // word_size
83 word_sep = '.'
84 word_fmt = '%.4x'
85 word_base = 16
86
87
88class eui64_bare(eui64_base):
89 """A bare (no delimiters) MAC address dialect class."""
90
91 word_size = 64
92 num_words = width // word_size
93 word_sep = ''
94 word_fmt = '%.16X'
95 word_base = 16
96
97
98#: The default dialect to be used when not specified by the user.
99
100DEFAULT_EUI64_DIALECT = eui64_base
101
102# -----------------------------------------------------------------------------
103#: Regular expressions to match all supported MAC address formats.
104#: For efficiency, each string regexp converted in place to its compiled
105#: counterpart.
106RE_EUI64_FORMATS = [
107 _re.compile(_, _re.IGNORECASE)
108 for _ in (
109 # 2 bytes x 8 (UNIX, Windows, EUI-64)
110 '^' + ':'.join(['([0-9A-F]{1,2})'] * 8) + '$',
111 '^' + '-'.join(['([0-9A-F]{1,2})'] * 8) + '$',
112 # 4 bytes x 4 (Cisco like)
113 '^' + ':'.join(['([0-9A-F]{1,4})'] * 4) + '$',
114 '^' + '-'.join(['([0-9A-F]{1,4})'] * 4) + '$',
115 '^' + r'\.'.join(['([0-9A-F]{1,4})'] * 4) + '$',
116 # 16 bytes (bare, no delimiters)
117 '^(' + ''.join(['[0-9A-F]'] * 16) + ')$',
118 )
119]
120
121
122def _get_match_result(address, formats):
123 for regexp in formats:
124 match = regexp.findall(address)
125 if match:
126 return match[0]
127
128
129def valid_str(addr):
130 """
131 :param addr: An IEEE EUI-64 identifier in string form.
132
133 :return: ``True`` if EUI-64 identifier is valid, ``False`` otherwise.
134 """
135 try:
136 if _get_match_result(addr, RE_EUI64_FORMATS):
137 return True
138 except TypeError:
139 pass
140
141 return False
142
143
144def str_to_int(addr):
145 """
146 :param addr: An IEEE EUI-64 identifier in string form.
147
148 :return: An unsigned integer that is equivalent to value represented
149 by EUI-64 string address formatted according to the dialect
150 """
151 words = []
152
153 try:
154 words = _get_match_result(addr, RE_EUI64_FORMATS)
155 if not words:
156 raise TypeError
157 except TypeError:
158 raise AddrFormatError('invalid IEEE EUI-64 identifier: %r!' % (addr,))
159
160 if isinstance(words, tuple):
161 pass
162 else:
163 words = (words,)
164
165 if len(words) == 8:
166 # 2 bytes x 8 (UNIX, Windows, EUI-48)
167 int_val = int(''.join(['%.2x' % int(w, 16) for w in words]), 16)
168 elif len(words) == 4:
169 # 4 bytes x 4 (Cisco like)
170 int_val = int(''.join(['%.4x' % int(w, 16) for w in words]), 16)
171 elif len(words) == 1:
172 # 16 bytes (bare, no delimiters)
173 int_val = int('%016x' % int(words[0], 16), 16)
174 else:
175 raise AddrFormatError('bad word count for EUI-64 identifier: %r!' % addr)
176
177 return int_val
178
179
180def int_to_str(int_val, dialect=None):
181 """
182 :param int_val: An unsigned integer.
183
184 :param dialect: (optional) a Python class defining formatting options
185
186 :return: An IEEE EUI-64 identifier that is equivalent to unsigned integer.
187 """
188 if dialect is None:
189 dialect = eui64_base
190 words = int_to_words(int_val, dialect)
191 tokens = [dialect.word_fmt % i for i in words]
192 addr = dialect.word_sep.join(tokens)
193 return addr
194
195
196def int_to_packed(int_val):
197 """
198 :param int_val: the integer to be packed.
199
200 :return: a packed string that is equivalent to value represented by an
201 unsigned integer.
202 """
203 words = int_to_words(int_val)
204 return _struct.pack('>8B', *words)
205
206
207def packed_to_int(packed_int):
208 """
209 :param packed_int: a packed string containing an unsigned integer.
210 It is assumed that string is packed in network byte order.
211
212 :return: An unsigned integer equivalent to value of network address
213 represented by packed binary string.
214 """
215 words = list(_struct.unpack('>8B', packed_int))
216
217 int_val = 0
218 for i, num in enumerate(reversed(words)):
219 word = num
220 word = word << 8 * i
221 int_val = int_val | word
222
223 return int_val
224
225
226def valid_words(words, dialect=None):
227 if dialect is None:
228 dialect = DEFAULT_EUI64_DIALECT
229 return _valid_words(words, dialect.word_size, dialect.num_words)
230
231
232def int_to_words(int_val, dialect=None):
233 if dialect is None:
234 dialect = DEFAULT_EUI64_DIALECT
235 return _int_to_words(int_val, dialect.word_size, dialect.num_words)
236
237
238def words_to_int(words, dialect=None):
239 if dialect is None:
240 dialect = DEFAULT_EUI64_DIALECT
241 return _words_to_int(words, dialect.word_size, dialect.num_words)
242
243
244def valid_bits(bits, dialect=None):
245 if dialect is None:
246 dialect = DEFAULT_EUI64_DIALECT
247 return _valid_bits(bits, width, dialect.word_sep)
248
249
250def bits_to_int(bits, dialect=None):
251 if dialect is None:
252 dialect = DEFAULT_EUI64_DIALECT
253 return _bits_to_int(bits, width, dialect.word_sep)
254
255
256def int_to_bits(int_val, dialect=None):
257 if dialect is None:
258 dialect = DEFAULT_EUI64_DIALECT
259 return _int_to_bits(int_val, dialect.word_size, dialect.num_words, dialect.word_sep)
260
261
262def valid_bin(bin_val, dialect=None):
263 if dialect is None:
264 dialect = DEFAULT_EUI64_DIALECT
265 return _valid_bin(bin_val, width)
266
267
268def int_to_bin(int_val):
269 return _int_to_bin(int_val, width)
270
271
272def bin_to_int(bin_val):
273 return _bin_to_int(bin_val, width)