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)