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