diff options
Diffstat (limited to 'netaddr/strategy/eui48.py')
-rw-r--r-- | netaddr/strategy/eui48.py | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/netaddr/strategy/eui48.py b/netaddr/strategy/eui48.py new file mode 100644 index 0000000..5685e34 --- /dev/null +++ b/netaddr/strategy/eui48.py @@ -0,0 +1,296 @@ +#----------------------------------------------------------------------------- +# Copyright (c) 2008 by David P. D. Moss. All rights reserved. +# +# Released under the BSD license. See the LICENSE file for details. +#----------------------------------------------------------------------------- +""" +IEEE 48-bit EUI (MAC address) logic. + +Supports numerous MAC string formats including Cisco's triple hextet as well +as bare MACs containing no delimiters. +""" +import struct as _struct +import re as _re + +# Check whether we need to use fallback code or not. +try: + from socket import AF_LINK +except ImportError: + AF_LINK = 48 + +from netaddr.core import AddrFormatError +from netaddr.compat import _is_str +from netaddr.strategy import ( + valid_words as _valid_words, int_to_words as _int_to_words, + words_to_int as _words_to_int, valid_bits as _valid_bits, + bits_to_int as _bits_to_int, int_to_bits as _int_to_bits, + valid_bin as _valid_bin, int_to_bin as _int_to_bin, + bin_to_int as _bin_to_int) + +#: The width (in bits) of this address type. +width = 48 + +#: The AF_* constant value of this address type. +family = AF_LINK + +#: A friendly string name address type. +family_name = 'MAC' + +#: The version of this address type. +version = 48 + +#: The maximum integer value that can be represented by this address type. +max_int = 2 ** width - 1 + +#----------------------------------------------------------------------------- +# Dialect classes. +#----------------------------------------------------------------------------- + +class mac_eui48(object): + """A standard IEEE EUI-48 dialect class.""" + #: The individual word size (in bits) of this address type. + word_size = 8 + + #: The number of words in this address type. + num_words = width // word_size + + #: The maximum integer value for an individual word in this address type. + max_word = 2 ** word_size - 1 + + #: The separator character used between each word. + word_sep = '-' + + #: The format string to be used when converting words to string values. + word_fmt = '%.2X' + + #: The number base to be used when interpreting word values as integers. + word_base = 16 + + +class mac_unix(mac_eui48): + """A UNIX-style MAC address dialect class.""" + word_size = 8 + num_words = width // word_size + word_sep = ':' + word_fmt = '%x' + word_base = 16 + + +class mac_unix_expanded(mac_unix): + """A UNIX-style MAC address dialect class with leading zeroes.""" + word_fmt = '%.2x' + + +class mac_cisco(mac_eui48): + """A Cisco 'triple hextet' MAC address dialect class.""" + word_size = 16 + num_words = width // word_size + word_sep = '.' + word_fmt = '%.4x' + word_base = 16 + + +class mac_bare(mac_eui48): + """A bare (no delimiters) MAC address dialect class.""" + word_size = 48 + num_words = width // word_size + word_sep = '' + word_fmt = '%.12X' + word_base = 16 + + +class mac_pgsql(mac_eui48): + """A PostgreSQL style (2 x 24-bit words) MAC address dialect class.""" + word_size = 24 + num_words = width // word_size + word_sep = ':' + word_fmt = '%.6x' + word_base = 16 + +#: The default dialect to be used when not specified by the user. +DEFAULT_DIALECT = mac_eui48 + +#----------------------------------------------------------------------------- +#: Regular expressions to match all supported MAC address formats. +RE_MAC_FORMATS = ( + # 2 bytes x 6 (UNIX, Windows, EUI-48) + '^' + ':'.join(['([0-9A-F]{1,2})'] * 6) + '$', + '^' + '-'.join(['([0-9A-F]{1,2})'] * 6) + '$', + + # 4 bytes x 3 (Cisco) + '^' + ':'.join(['([0-9A-F]{1,4})'] * 3) + '$', + '^' + '-'.join(['([0-9A-F]{1,4})'] * 3) + '$', + '^' + r'\.'.join(['([0-9A-F]{1,4})'] * 3) + '$', + + # 6 bytes x 2 (PostgreSQL) + '^' + '-'.join(['([0-9A-F]{5,6})'] * 2) + '$', + '^' + ':'.join(['([0-9A-F]{5,6})'] * 2) + '$', + + # 12 bytes (bare, no delimiters) + '^(' + ''.join(['[0-9A-F]'] * 12) + ')$', + '^(' + ''.join(['[0-9A-F]'] * 11) + ')$', +) +# For efficiency, each string regexp converted in place to its compiled +# counterpart. +RE_MAC_FORMATS = [_re.compile(_, _re.IGNORECASE) for _ in RE_MAC_FORMATS] + + +def valid_str(addr): + """ + :param addr: An IEEE EUI-48 (MAC) address in string form. + + :return: ``True`` if MAC address string is valid, ``False`` otherwise. + """ + for regexp in RE_MAC_FORMATS: + try: + match_result = regexp.findall(addr) + if len(match_result) != 0: + return True + except TypeError: + pass + + return False + + +def str_to_int(addr): + """ + :param addr: An IEEE EUI-48 (MAC) address in string form. + + :return: An unsigned integer that is equivalent to value represented + by EUI-48/MAC string address formatted according to the dialect + settings. + """ + words = [] + if _is_str(addr): + found_match = False + for regexp in RE_MAC_FORMATS: + match_result = regexp.findall(addr) + if len(match_result) != 0: + found_match = True + if isinstance(match_result[0], tuple): + words = match_result[0] + else: + words = (match_result[0],) + break + if not found_match: + raise AddrFormatError('%r is not a supported MAC format!' % (addr,)) + else: + raise TypeError('%r is not str() or unicode()!' % (addr,)) + + int_val = None + + if len(words) == 6: + # 2 bytes x 6 (UNIX, Windows, EUI-48) + int_val = int(''.join(['%.2x' % int(w, 16) for w in words]), 16) + elif len(words) == 3: + # 4 bytes x 3 (Cisco) + int_val = int(''.join(['%.4x' % int(w, 16) for w in words]), 16) + elif len(words) == 2: + # 6 bytes x 2 (PostgreSQL) + int_val = int(''.join(['%.6x' % int(w, 16) for w in words]), 16) + elif len(words) == 1: + # 12 bytes (bare, no delimiters) + int_val = int('%012x' % int(words[0], 16), 16) + else: + raise AddrFormatError('unexpected word count in MAC address %r!' % (addr,)) + + return int_val + + +def int_to_str(int_val, dialect=None): + """ + :param int_val: An unsigned integer. + + :param dialect: (optional) a Python class defining formatting options. + + :return: An IEEE EUI-48 (MAC) address string that is equivalent to + unsigned integer formatted according to the dialect settings. + """ + if dialect is None: + dialect = mac_eui48 + + words = int_to_words(int_val, dialect) + tokens = [dialect.word_fmt % i for i in words] + addr = dialect.word_sep.join(tokens) + + return addr + + +def int_to_packed(int_val): + """ + :param int_val: the integer to be packed. + + :return: a packed string that is equivalent to value represented by an + unsigned integer. + """ + return _struct.pack(">HI", int_val >> 32, int_val & 0xffffffff) + + +def packed_to_int(packed_int): + """ + :param packed_int: a packed string containing an unsigned integer. + It is assumed that string is packed in network byte order. + + :return: An unsigned integer equivalent to value of network address + represented by packed binary string. + """ + words = list(_struct.unpack('>6B', packed_int)) + + int_val = 0 + for i, num in enumerate(reversed(words)): + word = num + word = word << 8 * i + int_val = int_val | word + + return int_val + + +def valid_words(words, dialect=None): + if dialect is None: + dialect = DEFAULT_DIALECT + return _valid_words(words, dialect.word_size, dialect.num_words) + + +def int_to_words(int_val, dialect=None): + if dialect is None: + dialect = DEFAULT_DIALECT + return _int_to_words(int_val, dialect.word_size, dialect.num_words) + + +def words_to_int(words, dialect=None): + if dialect is None: + dialect = DEFAULT_DIALECT + return _words_to_int(words, dialect.word_size, dialect.num_words) + + +def valid_bits(bits, dialect=None): + if dialect is None: + dialect = DEFAULT_DIALECT + return _valid_bits(bits, width, dialect.word_sep) + + +def bits_to_int(bits, dialect=None): + if dialect is None: + dialect = DEFAULT_DIALECT + return _bits_to_int(bits, width, dialect.word_sep) + + +def int_to_bits(int_val, dialect=None): + if dialect is None: + dialect = DEFAULT_DIALECT + return _int_to_bits( + int_val, dialect.word_size, dialect.num_words, dialect.word_sep) + + +def valid_bin(bin_val, dialect=None): + if dialect is None: + dialect = DEFAULT_DIALECT + return _valid_bin(bin_val, width) + + +def int_to_bin(int_val): + return _int_to_bin(int_val, width) + + +def bin_to_int(bin_val): + return _bin_to_int(bin_val, width) |