Package netaddr :: Module strategy |
[frames] | no frames]

# Source Code for Module netaddr.strategy

      1  #!/usr/bin/env python
      2  #-----------------------------------------------------------------------------
      3  #   Copyright (c) 2008, David P. D. Moss. All rights reserved.
      4  #
      5  #   Released under the BSD license. See the LICENSE file for details.
      6  #-----------------------------------------------------------------------------
      7  """
      8  network address conversion logic, constants and shared strategy objects.
      9  """
     10  import socket as _socket
     11  import struct as _struct
     12  import re as _re
     13
     14  USE_IPV4_OPT=True   #: Use optimised IPv4 strategy? Default: True
     15
     16  try:
     17      #   Detects a common bug in various Python implementations.
     18      _socket.inet_aton('255.255.255.255')
     19  except:
     20      USE_IPV4_OPT=False
     21
     22  from netaddr import BIG_ENDIAN_PLATFORM, AT_UNSPEC, AT_INET, AT_INET6, \
     23                      AT_LINK, AT_EUI64, AT_NAMES, AddrFormatError
     24
     25  #-----------------------------------------------------------------------------
     26 -def _BYTES_TO_BITS():
     27      """
     28      Generates a 256 element list of 8-bit binary digit strings. List index is
     29      equivalent to the bit string value.
     30      """
     31      lookup = []
     32      bits_per_byte = range(7, -1, -1)
     33      for num in range(256):
     34          bits = 8*[None]
     35          for i in bits_per_byte:
     36              bits[i] = '01'[num&1]
     37              num >>= 1
     38          lookup.append(''.join(bits))
     39      return lookup
     40
     41  _BYTES_TO_BITS = _BYTES_TO_BITS()
     42
     43  #-----------------------------------------------------------------------------
     44 -class AddrStrategy(object):
     45      """Basic support for common operations performed on each address type"""
     46
     47      #: Lookup table for struct module format strings.
     48      STRUCT_FORMATS = {
     49           8 : 'B',   # unsigned char
     50          16 : 'H',   # unsigned short
     51          32 : 'I',   # unsigned int
     52      }
     53
     54 -    def __init__(self, width, word_size, word_sep, word_fmt='%x',
     55                   addr_type=AT_UNSPEC, word_base=16, uppercase=False):
     56          """
     57          Constructor.
     58
     59          @param width: size of address in bits.
     60              (e.g. 32 - IPv4, 48 - MAC, 128 - IPv6)
     61
     62          @param word_size: size of each word.
     63              (e.g. 8 - octets, 16 - hextets)
     64
     65          @param word_sep: separator between each word.
     66              (e.g. '.' - IPv4, ':' - IPv6, '-' - EUI-48)
     67
     68          @param word_fmt: format string for each word.
     69              (Default: '%x')
     70
     71          @param addr_type: address type.
     72              (Default: AT_UNSPEC)
     73
     74          @param word_base: number base used to convert each word using int().
     75              (Default: 16)
     76
     77          @param uppercase: uppercase address.
     78              (Default: False)
     79          """
     80
     81          self.width = width
     82          self.max_int = 2 ** width - 1
     83          self.word_size = word_size
     84          self.num_words = width / word_size
     85          self.max_word = 2 ** word_size - 1
     86          self.word_sep = word_sep
     87          self.word_fmt  = word_fmt
     88          self.word_base = word_base
     89          self.addr_type = addr_type
     90          self.uppercase = uppercase
     91
     92          try:
     93              self.name = AT_NAMES[addr_type]
     94          except KeyError:
     95              self.name = AT_NAMES[AT_UNSPEC]
     96
     97 -    def __repr__(self):
     98          """@return: executable Python string to recreate equivalent object"""
     99          return "%s(%r, %r, %r, %r, %r, %r)" %  (self.__class__.__name__,
    100              self.width, self.word_size, self.word_sep, self.addr_type,
    101              self.word_base, self.uppercase)
    102
    103      #-------------------------------------------------------------------------
    104      #   Binary methods.
    105      #-------------------------------------------------------------------------
    106
    107 -    def valid_bits(self, bits):
    108          """
    109          @param bits: A network address in readable binary form.
    110
    111          @return: C{True} if network address is valid for this address type,
    112              C{False} otherwise.
    113          """
    114          if not isinstance(bits, (str, unicode)):
    115              return False
    116
    117          bits = bits.replace(self.word_sep, '')
    118
    119          if len(bits) != self.width:
    120              return False
    121
    122          try:
    123              if 0 <= int(bits, 2) <= self.max_int:
    124                  return True
    125          except ValueError:
    126              return False
    127          return False
    128
    129 -    def bits_to_int(self, bits):
    130          """
    131          @param bits: A network address in readable binary form.
    132
    133          @return: A network byte order integer that is equivalent to value
    134              represented by network address in readable binary form.
    135          """
    136          if not self.valid_bits(bits):
    137              raise ValueError('%r is not a valid binary form string for ' \
    138                  'address type!' % bits)
    139
    140          word_bits = bits.split(self.word_sep)
    141          if len(word_bits) != self.num_words:
    142              raise ValueError('invalid number of words within binary form ' \
    143                  'string for address type!' % bits)
    144
    145          words = tuple([int(i, 2) for i in word_bits])
    146
    147          return self.words_to_int(words)
    148
    149      #-------------------------------------------------------------------------
    150      #   Integer methods.
    151      #-------------------------------------------------------------------------
    152
    153 -    def valid_int(self, int_val):
    154          """
    155          @param int_val: A network byte order integer.
    156
    157          @return: C{True} if network byte order integer falls within the
    158              boundaries of this address type, C{False} otherwise.
    159          """
    160          if not isinstance(int_val, (int, long)):
    161              return False
    162
    163          return 0 <= int_val <= self.max_int
    164
    165 -    def int_to_str(self, int_val):
    166          """
    167          @param int_val: A network byte order integer.
    168
    169          @return: A network address in string form that is equivalent to value
    170              represented by a network byte order integer.
    171          """
    172          words = self.int_to_words(int_val)
    173          tokens = [self.word_fmt % i for i in words]
    174          addr = self.word_sep.join(tokens)
    175
    176          if self.uppercase is True:
    177              return addr.upper()
    178
    179          return addr
    180
    181 -    def int_to_bits(self, int_val):
    182          """
    183          @param int_val: A network byte order integer.
    184
    185          @return: A network address in readable binary form that is equivalent
    186              to value represented by a network byte order integer.
    187          """
    188          bit_words = []
    189
    190          for word in self.int_to_words(int_val):
    191              bits = []
    192              while word:
    193                  bits.append(_BYTES_TO_BITS[word&255])
    194                  word >>= 8
    195              bits.reverse()
    196              bit_str = ''.join(bits) or '0'*self.word_size
    197              bits = ('0'*self.word_size+bit_str)[-self.word_size:]
    198              bit_words.append(bits)
    199
    200          return self.word_sep.join(bit_words)
    201
    202 -    def int_to_bin(self, int_val):
    203          """
    204          @param int_val: A network byte order integer.
    205
    206          @return: A network address in standard binary representation format
    207              that is equivalent to integer address value. Essentially a back
    208              port of the bin() builtin in Python 2.6.x and higher.
    209          """
    210          bit_words = []
    211
    212          for word in self.int_to_words(int_val):
    213              bits = []
    214              while word:
    215                  bits.append(_BYTES_TO_BITS[word&255])
    216                  word >>= 8
    217              bits.reverse()
    218              bit_str = ''.join(bits) or '0'*self.word_size
    219              bits = ('0'*self.word_size+bit_str)[-self.word_size:]
    220              bit_words.append(bits)
    221
    222          return '0b' + ''.join(bit_words)
    223
    224 -    def int_to_words(self, int_val, num_words=None, word_size=None):
    225          """
    226          @param int_val: A network byte order integer to be split.
    227
    228          @param num_words: (optional) number of words expected in return value
    229              tuple. Uses address type default if not specified.
    230
    231          @param word_size: (optional) size/width of individual words (in bits).
    232               Uses address type default if not specified.
    233          """
    234          if not self.valid_int(int_val):
    235              raise IndexError('integer %r is out of bounds!' % hex(int_val))
    236
    237          #   Set defaults for optional args.
    238          if num_words is None:
    239              num_words = self.num_words
    240          if word_size is None:
    241              word_size = self.word_size
    242
    243          max_word_size = 2 ** word_size - 1
    244
    245          words = []
    246          for _ in range(num_words):
    247              word = int_val & max_word_size
    248              words.append(int(word))
    249              int_val >>= word_size
    250          words.reverse()
    251
    252          return tuple(words)
    253
    254 -    def int_to_packed(self, int_val):
    255          """
    256          @param int_val: the integer to be packed.
    257
    258          @return: a packed string that is equivalent to value represented by a
    259          network byte order integer.
    260          """
    261
    262          words = self.int_to_words(int_val, self.num_words, self.word_size)
    263
    264          try:
    265              fmt = '>%d%s' % (self.num_words, AddrStrategy.STRUCT_FORMATS[
    266                  self.word_size])
    267          except KeyError:
    268              raise ValueError('unsupported word size: %d!' % self.word_size)
    269
    270          return _struct.pack(fmt, *words)
    271
    272
    273      #-------------------------------------------------------------------------
    274      #   Packed string methods.
    275      #-------------------------------------------------------------------------
    276
    277 -    def packed_to_int(self, packed_int):
    278          """
    279          @param packed_int: a packed string containing an unsigned integer.
    280              Network byte order is assumed.
    281
    282          @return: A network byte order integer that is equivalent to value
    283              of network address represented by packed binary string.
    284          """
    285          try:
    286              fmt = '>%d%s' % (self.num_words, AddrStrategy.STRUCT_FORMATS[
    287                  self.word_size])
    288          except KeyError:
    289              raise ValueError('unsupported word size: %d!' % self.word_size)
    290
    291          words = list(_struct.unpack(fmt, packed_int))
    292          words.reverse()
    293
    294          int_val = 0
    295          for i, num in enumerate(words):
    296              word = num
    297              word = word << self.word_size * i
    298              int_val = int_val | word
    299
    300          return int_val
    301
    302      #-------------------------------------------------------------------------
    303      #   String methods.
    304      #-------------------------------------------------------------------------
    305
    306 -    def valid_str(self, addr):
    307          """
    308          @param addr: A network address in string form.
    309
    310          @return: C{True} if network address in string form is valid for this
    311              address type, C{False} otherwise.
    312          """
    313          if not isinstance(addr, (str, unicode)):
    314              return False
    315
    316          tokens = addr.split(self.word_sep)
    317          if len(tokens) != self.num_words:
    318              return False
    319
    320          try:
    321              for token in tokens:
    322                  if not 0 <= int(token, self.word_base) <= \
    323                         self.max_word:
    324                      return False
    325          except TypeError:
    326              return False
    327          except ValueError:
    328              return False
    329          return True
    330
    331 -    def str_to_int(self, addr):
    332          """
    333          @param addr: A network address in string form.
    334
    335          @return: A network byte order integer that is equivalent to value
    336              represented by network address in string form.
    337          """
    338          if not self.valid_str(addr):
    339              raise ValueError('%r is not a recognised string representation' \
    340                  ' of this address type!' % addr)
    341
    342          tokens = addr.split(self.word_sep)
    343          words = [ int(token, self.word_base) for token in tokens ]
    344
    345          return self.words_to_int(words)
    346
    347      #-------------------------------------------------------------------------
    348      #   Word list methods.
    349      #-------------------------------------------------------------------------
    350
    351 -    def valid_words(self, words):
    352          """
    353          @param words: A sequence containing integer word values.
    354
    355          @return: C{True} if word sequence is valid for this address type,
    356              C{False} otherwise.
    357          """
    358          if not hasattr(words, '__iter__'):
    359              return False
    360
    361          if len(words) != self.num_words:
    362              return False
    363
    364          for i in words:
    365              if not isinstance(i, (int, long)):
    366                  return False
    367
    368              if not 0 <= i <= self.max_word:
    369                  return False
    370          return True
    371
    372 -    def words_to_int(self, words):
    373          """
    374          @param words: A list or tuple containing integer word values.
    375
    376          @return: A network byte order integer that is equivalent to value
    377              represented by word sequence.
    378          """
    379          if not self.valid_words(words):
    380              raise ValueError('%r is not a valid word list!' % words)
    381
    382          #   tuples have no reverse() method and reversed() is only available
    383          #   in Python 2.4. Ugly but necessary.
    384          if isinstance(words, tuple):
    385              words = list(words)
    386          words.reverse()
    387
    388          int_val = 0
    389          for i, num in enumerate(words):
    390              word = num
    391              word = word << self.word_size * i
    392              int_val = int_val | word
    393
    394          return int_val
    395
    396  #-----------------------------------------------------------------------------
    397 -class IPv4StrategyStd(AddrStrategy):
    398      """
    399      A 'safe' L{AddrStrategy} for IPv4 addresses. Unlike L{IPv4StrategyOpt}.
    400
    401      It contains all methods related to IPv4 addresses that the optimised
    402      version has, without the reliance on the socket or struct modules. There
    403      are several cases where the use of this class are preferable when either
    404      the modules mentioned do not exist on certain Python implementations or
    405      contain bugs like the infamous inet_aton('255.255.255.254') bug.
    406
    407      All methods shared between the optimised class and this one should be
    408      defined here.
    409      """
    410 -    def __init__(self):
    411          """Constructor."""
    412          super(IPv4StrategyStd, self).__init__(width=32, word_size=8,
    413                word_fmt='%d', word_sep='.', addr_type=AT_INET, word_base=10)
    414
    415 -    def int_to_arpa(self, int_val):
    416          """
    417          @param int_val: A network byte order integer.
    418
    419          @return: The reverse DNS lookup for an IPv4 address in network byte
    420              order integer form.
    421          """
    422          words = ["%d" % i for i in self.int_to_words(int_val)]
    423          words.reverse()
    424          words.extend(['in-addr', 'arpa', ''])
    425          return '.'.join(words)
    426
    427  #-----------------------------------------------------------------------------
    428 -class IPv4StrategyOpt(IPv4StrategyStd):
    429      """
    430      An optimised L{AddrStrategy} for IPv4 addresses.
    431
    432      It uses C{pack()} and C{unpack()} from the C{struct} module along with the
    433      C{inet_ntoa()} and C{inet_aton()} from the C{socket} module great improve
    434      the speed of certain operations (approx. 2.5 times faster than a standard
    435      L{AddrStrategy} configured for IPv4).
    436
    437      However, keep in mind that these modules might not be available everywhere
    438      that Python itself is. Runtimes such as Google App Engine gut the
    439      C{socket} module. C{struct} is also limited to processing 32-bit integers
    440      which is fine for IPv4 but isn't suitable for IPv6.
    441      """
    442 -    def __init__(self):
    443          """Constructor."""
    444          super(IPv4StrategyOpt, self).__init__()
    445
    446 -    def valid_str(self, addr):
    447          """
    448          @param addr: An IP address in presentation (string) format.
    449
    450          @return: C{True} if network address in string form is valid for this
    451              address type, C{False} otherwise.
    452          """
    453          try:
    454              #   This call handles a lot of older IPv4 address formats that
    455              #   it would be a lot of work to implement (many edge cases).
    456              #   Please Note: only this optimised strategy class will have
    457              #   the ability to parse these less common formats for the
    458              #   moment.
    459              _socket.inet_aton(addr)
    460          except _socket.error:
    461              return False
    462          except TypeError:
    463              return False
    464          return True
    465
    466 -    def str_to_int(self, addr):
    467          """
    468          @param addr: An IPv4 dotted decimal address in string form.
    469
    470          @return: A network byte order integer that is equivalent to value
    471              represented by the IPv4 dotted decimal address string.
    472          """
    473          if not self.valid_str(addr):
    474              raise ValueError('%r is not a valid IPv4 address string!' \
    475                  % addr)
    476          return _struct.unpack('>I', _socket.inet_aton(addr))[0]
    477
    478 -    def int_to_str(self, int_val):
    479          """
    480          @param int_val: A network byte order integer.
    481
    482          @return: An IPv4 dotted decimal address string that is equivalent to
    483              value represented by a 32 bit integer in network byte order.
    484          """
    485          if not self.valid_int(int_val):
    486              raise ValueError('%r is not a valid 32-bit integer!' % int_val)
    487          return _socket.inet_ntoa(_struct.pack('>I', int_val))
    488
    489 -    def int_to_words(self, int_val, num_words=None, word_size=None):
    490          """
    491          @param int_val: A network byte order integer.
    492
    493          @param num_words: (unused) *** interface compatibility only ***
    494
    495          @param word_size: (unused) *** interface compatibility only ***
    496
    497          @return: An integer word (octet) sequence that is equivalent to value
    498              represented by network byte order integer.
    499          """
    500          if not self.valid_int(int_val):
    501              raise ValueError('%r is not a valid integer value supported ' \
    502                  'by this address type!' % int_val)
    503          return _struct.unpack('4B', _struct.pack('>I', int_val))
    504
    505 -    def words_to_int(self, octets):
    506          """
    507          @param octets: A list or tuple containing integer octets.
    508
    509          @return: A network byte order integer that is equivalent to value
    510              represented by word (octet) sequence.
    511          """
    512          if not self.valid_words(octets):
    513              raise ValueError('%r is not a valid octet list for an IPv4 ' \
    514                  'address!' % octets)
    515          return _struct.unpack('>I', _struct.pack('4B', *octets))[0]
    516
    517  #-----------------------------------------------------------------------------
    518 -class IPv6Strategy(AddrStrategy):
    519      """
    520      Implements the operations that can be performed on an Internet Protocol
    521      version 6 network address in accordance with RFC 4291.
    522
    523      NB - This class would benefit greatly from access to inet_pton/inet_ntop()
    524      function calls in Python's socket module. Sadly, they aren't available so
    525      we'll have to put up with the pure-Python implementation here (for now at
    526      least).
    527      """
    528 -    def __init__(self):
    529          """Constructor."""
    530          super(self.__class__, self).__init__(addr_type=AT_INET6,
    531              width=128, word_size=16, word_fmt='%x', word_sep=':')
    532
    533 -    def valid_str(self, addr):
    534          """
    535          @param addr: An IPv6 address in string form.
    536
    537          @return: C{True} if IPv6 network address string is valid, C{False}
    538              otherwise.
    539          """
    540          #TODO: Reduce the length of this method ...
    541          if not isinstance(addr, (str, unicode)):
    542              return False
    543
    544          if '::' in addr:
    545              #   IPv6 compact mode.
    546              try:
    547                  prefix, suffix = addr.split('::')
    548              except ValueError:
    549                  return False
    550
    551              l_prefix = []
    552              l_suffix = []
    553
    554              if prefix != '':
    555                  l_prefix = prefix.split(':')
    556
    557              if suffix != '':
    558                  l_suffix = suffix.split(':')
    559
    560              #   IPv6 compact IPv4 compatibility mode.
    561              if len(l_suffix) and '.' in l_suffix[-1]:
    562                  ipv4_str = l_suffix[-1]
    563                  if ST_IPV4.valid_str(ipv4_str):
    564                      ipv4_int = ST_IPV4.str_to_int(ipv4_str)
    565                      ipv4_words = ST_IPV4.int_to_words(ipv4_int)
    566                      l_suffix.pop()
    567                      l_suffix.append(
    568                          ''.join(["%.2x" % i for i in ipv4_words[0:2]]))
    569                      l_suffix.append(
    570                          ''.join(["%.2x" % i for i in ipv4_words[2:4]]))
    571
    572              token_count = len(l_prefix) + len(l_suffix)
    573
    574              if not 0 <= token_count <= self.num_words - 1:
    575                  return False
    576
    577              try:
    578                  for token in l_prefix + l_suffix:
    579                      word = int(token, 16)
    580                      if not 0 <= word <= self.max_word:
    581                          return False
    582              except ValueError:
    583                  return False
    584          else:
    585              #   IPv6 verbose mode.
    586              if ':' in addr:
    587                  tokens = addr.split(':')
    588
    589                  if '.' in addr:
    590                      ipv6_prefix = tokens[:-1]
    591                      if ipv6_prefix[:-1] != ['0', '0', '0', '0', '0']:
    592                          return False
    593                      if ipv6_prefix[-1].lower() not in ('0', 'ffff'):
    594                          return False
    595                      #   IPv6 verbose IPv4 compatibility mode.
    596                      if len(tokens) != (self.num_words - 1):
    597                          return False
    598                      ipv4_str = tokens[-1]
    599                      if ST_IPV4.valid_str(ipv4_str):
    600                          ipv4_int = ST_IPV4.str_to_int(ipv4_str)
    601                          ipv4_words = ST_IPV4.int_to_words(ipv4_int)
    602                          tokens.pop()
    603                          tokens.append(
    604                              ''.join(["%.2x" % i for i in ipv4_words[0:2]]))
    605                          tokens.append(
    606                              ''.join(["%.2x" % i for i in ipv4_words[2:4]]))
    607                  else:
    608                      #   IPv6 verbose mode.
    609                      if len(tokens) != self.num_words:
    610                          return False
    611                  try:
    612                      for token in tokens:
    613                          word = int(token, 16)
    614                          if not 0 <= word <= self.max_word:
    615                              return False
    616                  except ValueError:
    617                      return False
    618              else:
    619                  return False
    620
    621          return True
    622
    623 -    def str_to_int(self, addr):
    624          """
    625          @param addr: An IPv6 address in string form.
    626
    627          @return: The equivalent network byte order integer for a given IPv6
    628              address.
    629          """
    630          if not self.valid_str(addr):
    631              raise ValueError("'%s' is an invalid IPv6 address!" % addr)
    632
    633          values = []
    634
    635          if addr == '::':
    636              #   Unspecified address.
    637              return 0
    638          elif '::' in addr:
    639              #   Abbreviated form IPv6 address.
    640              prefix, suffix = addr.split('::')
    641
    642              if prefix == '':
    643                  l_prefix = ['0']
    644              else:
    645                  l_prefix = prefix.split(':')
    646
    647              if suffix == '':
    648                  l_suffix = ['0']
    649              else:
    650                  l_suffix = suffix.split(':')
    651
    652              #   Check for IPv4 compatibility address form.
    653              if len(l_suffix) and '.' in l_suffix[-1]:
    654                  if len(l_suffix) > 2:
    655                      return False
    656                  if len(l_suffix) == 2 and l_suffix[0].lower() != 'ffff':
    657                      return False
    658
    659                  ipv4_str = l_suffix[-1]
    660                  if ST_IPV4.valid_str(ipv4_str):
    661                      ipv4_int = ST_IPV4.str_to_int(ipv4_str)
    662                      ipv4_words = ST_IPV4.int_to_words(ipv4_int)
    663                      l_suffix.pop()
    664                      l_suffix.append(
    665                          ''.join(["%.2x" % i for i in ipv4_words[0:2]]))
    666                      l_suffix.append(
    667                          ''.join(["%.2x" % i for i in ipv4_words[2:4]]))
    668
    669              gap_size = 8 - ( len(l_prefix) + len(l_suffix) )
    670
    671              values = ["%04x" % int(i, 16) for i in l_prefix] \
    672                     + ['0000' for i in range(gap_size)] \
    673                     + ["%04x" % int(i, 16) for i in l_suffix]
    674          else:
    675              #   Verbose form IPv6 address.
    676              if '.' in addr:
    677                  #   IPv4 compatiblility mode.
    678                  tokens = addr.split(':')
    679                  ipv4_str = tokens[-1]
    680                  if ST_IPV4.valid_str(ipv4_str):
    681                      ipv4_int = ST_IPV4.str_to_int(ipv4_str)
    682                      ipv4_words = ST_IPV4.int_to_words(ipv4_int)
    683                      tokens.pop()
    684                      tokens.append(''.join(["%.2x" % i for i in ipv4_words[0:2]]))
    685                      tokens.append(''.join(["%.2x" % i for i in ipv4_words[2:4]]))
    686
    687                  values = ["%04x" % int(i, 16) for i in tokens]
    688              else:
    689                  #   non IPv4 compatiblility mode.
    690                  values = ["%04x" % int(i, 16) for i in addr.split(':')]
    691
    692          value = int(''.join(values), 16)
    693
    694          return value
    695
    696 -    def int_to_str(self, int_val, compact=True, word_fmt=None):
    697          """
    698          @param int_val: A network byte order integer.
    699
    700          @param compact: (optional) A boolean flag indicating if compact
    701              formatting should be used. If True, this method uses the '::'
    702              string to represent the first adjacent group of words with a value
    703              of zero. Default: True
    704
    705          @param word_fmt: (optional) The Python format string used to override
    706              formatting for each word.
    707
    708          @return: The IPv6 string form equal to the network byte order integer
    709          value provided.
    710          """
    711          #   Use basic parent class implementation if compact string form is
    712          #   not required.
    713          if not compact:
    714              return super(self.__class__, self).int_to_str(int_val)
    715
    716          _word_fmt = self.word_fmt
    717          if word_fmt is not None:
    718              _word_fmt = word_fmt
    719
    720          if not self.valid_int(int_val):
    721              raise ValueError('%r is not a valid integer value supported ' \
    722                  'by this address type!' % int_val)
    723
    724          tokens = []
    725          for i in range(self.num_words):
    726              word = int_val & (2 ** self.word_size - 1)
    727              tokens += [_word_fmt % word]
    728              int_val >>= self.word_size
    729
    730          tokens.reverse()
    731
    732          if compact == True:
    733              new_tokens = []
    734
    735              positions = []
    736              within_run = False
    737              start_index = None
    738              num_tokens = 0
    739
    740              #   Discover all runs of zeros.
    741              for idx, token in enumerate(tokens):
    742                  if token == '0':
    743                      within_run = True
    744                      if start_index is None:
    745                          start_index = idx
    746                      num_tokens += 1
    747                  else:
    748                      if num_tokens > 1:
    749                          positions.append((num_tokens, start_index))
    750                      within_run = False
    751                      start_index = None
    752                      num_tokens = 0
    753
    754                  new_tokens.append(token)
    755
    756              #   Store any position not saved before loop exit.
    757              if num_tokens > 1:
    758                  positions.append((num_tokens, start_index))
    759
    760              #   Replace first longest run with an empty string.
    761              if len(positions) != 0:
    762                  #   Locate longest, left-most run of zeros.
    763                  positions.sort(lambda x, y: cmp(x[1], y[1]))
    764                  best_position = positions[0]
    765                  for position in positions:
    766                      if position[0] > best_position[0]:
    767                          best_position = position
    768                  #   Replace chosen zero run.
    769                  (length, start_idx) = best_position
    770                  new_tokens = new_tokens[0:start_idx] + [''] + \
    771                               new_tokens[start_idx+length:]
    772
    773                  #   Add start and end blanks so join creates '::'.
    774                  if new_tokens[0] == '':
    775                      new_tokens.insert(0, '')
    776
    777                  if new_tokens[-1] == '':
    778                      new_tokens.append('')
    779
    780              tokens = new_tokens
    781
    782          return self.word_sep.join(tokens)
    783
    784 -    def int_to_arpa(self, int_val):
    785          """
    786          @param int_val: A network byte order integer.
    787
    788          @return: The reverse DNS lookup for an IPv6 address in network byte
    789              order integer form.
    790          """
    791          addr = self.int_to_str(int_val, word_fmt='%04x')
    792          tokens = list(addr.replace(':', ''))
    793          tokens.reverse()
    794          #   We won't support ip6.int here - see RFC 3152 for details.
    795          tokens = tokens + ['ip6', 'arpa', '']
    796          return '.'.join(tokens)
    797
    798  #-----------------------------------------------------------------------------
    799 -class EUI48Strategy(AddrStrategy):
    800      """
    801      Implements the operations that can be performed on an IEEE 48-bit EUI
    802      (Extended Unique Identifer) a.k.a. a MAC (Media Access Control) layer 2
    803      address.
    804
    805      Supports all common (and some less common MAC string formats including
    806      Cisco's 'triple hextet' format and also bare MACs that contain no
    807      delimiters.
    808      """
    809      #   Regular expression to match the numerous different MAC formats.
    810      RE_MAC_FORMATS = (
    811          #   2 bytes x 6 (UNIX, Windows, EUI-48)
    812          '^' + ':'.join(['([0-9A-F]{1,2})'] * 6) + '$',
    813          '^' + '-'.join(['([0-9A-F]{1,2})'] * 6) + '$',
    814
    815          #   4 bytes x 3 (Cisco)
    816          '^' + ':'.join(['([0-9A-F]{1,4})'] * 3) + '$',
    817          '^' + '-'.join(['([0-9A-F]{1,4})'] * 3) + '$',
    818
    819          #   6 bytes x 2 (PostgreSQL)
    820          '^' + '-'.join(['([0-9A-F]{5,6})'] * 2) + '$',
    821          '^' + ':'.join(['([0-9A-F]{5,6})'] * 2) + '$',
    822
    823          #   12 bytes (bare, no delimiters)
    824          '^(' + ''.join(['[0-9A-F]'] * 12) + ')$',
    825          '^(' + ''.join(['[0-9A-F]'] * 11) + ')$',
    826      )
    827      #   For efficiency, replace each string regexp with its compiled
    828      #   equivalent.
    829      RE_MAC_FORMATS = [_re.compile(_, _re.IGNORECASE) for _ in RE_MAC_FORMATS]
    830
    831 -    def __init__(self, word_fmt='%02x', word_sep='-', uppercase=True):
    832          """
    833          Constructor.
    834
    835          @param word_sep: separator between each word.
    836              (Default: '-')
    837
    838          @param word_fmt: format string for each hextet.
    839              (Default: '%02x')
    840
    841          @param uppercase: return uppercase MAC/EUI-48 addresses.
    842              (Default: True)
    843          """
    844          super(self.__class__, self).__init__(addr_type=AT_LINK, width=48,
    845                word_size=8, word_fmt=word_fmt, word_sep=word_sep,
    846                uppercase=uppercase)
    847
    848 -    def reset(self):
    849          """
    850          Resets the internal state of this strategy to safe default values.
    851          """
    852          #   These are the settings for EUI-48 specific formatting.
    853          self.width = 48
    854          self.max_int = 2 ** self.width - 1
    855          self.word_size = 8
    856          self.num_words = self.width / self.word_size
    857          self.max_word = 2 ** self.word_size - 1
    858          self.word_sep = '-'
    859          self.word_fmt  = '%02x'
    860          self.word_base = 16
    861          self.addr_type = AT_LINK
    862          self.uppercase = True
    863
    864 -    def valid_str(self, addr):
    865          """
    866          @param addr: An EUI-48 or MAC address in string form.
    867
    868          @return: C{True} if MAC address string is valid, C{False} otherwise.
    869          """
    870          if not isinstance(addr, (str, unicode)):
    871              return False
    872
    873          for regexp in EUI48Strategy.RE_MAC_FORMATS:
    874              match_result = regexp.findall(addr)
    875              if len(match_result) != 0:
    876                  return True
    877          return False
    878
    879 -    def str_to_int(self, addr):
    880          """
    881          @param addr: An EUI-48 or MAC address in string form.
    882
    883          @return: A network byte order integer that is equivalent to value
    884              represented by EUI-48/MAC string address.
    885          """
    886          words = []
    887          if isinstance(addr, (str, unicode)):
    888              found_match = False
    889              for regexp in EUI48Strategy.RE_MAC_FORMATS:
    890                  match_result = regexp.findall(addr)
    891                  if len(match_result) != 0:
    892                      found_match = True
    893                      if isinstance(match_result[0], tuple):
    894                          words = match_result[0]
    895                      else:
    896                          words = (match_result[0],)
    897                      break
    898              if not found_match:
    899                  raise AddrFormatError('%r is not a supported MAC format!' \
    900                      % addr)
    901          else:
    902              raise TypeError('%r is not str() or unicode()!' % addr)
    903
    904          int_val = None
    905
    906          if len(words) == 6:
    907              #   2 bytes x 6 (UNIX, Windows, EUI-48)
    908              int_val = int(''.join(['%02x' % int(w, 16) for w in words]), 16)
    909          elif len(words) == 3:
    910              #   4 bytes x 3 (Cisco)
    911              int_val = int(''.join(['%04x' % int(w, 16) for w in words]), 16)
    912          elif len(words) == 2:
    913              #   6 bytes x 2 (PostgreSQL)
    914              int_val = int(''.join(['%06x' % int(w, 16) for w in words]), 16)
    915          elif len(words) == 1:
    916              #   12 bytes (bare, no delimiters)
    917              int_val = int('%012x' % int(words[0], 16), 16)
    918          else:
    919              raise AddrFormatError('unexpected word count in MAC address %r!' \
    920                  % addr)
    921
    922          return int_val
    923
    924 -    def int_to_str(self, int_val, word_sep=None, word_fmt=None):
    925          """
    926          @param int_val: A network byte order integer.
    927
    928          @param word_sep: (optional) The separator used between words in an
    929              address string.
    930
    931          @param word_fmt: (optional) A Python format string used to format
    932              each word of address.
    933
    934          @return: A MAC address in string form that is equivalent to value
    935              represented by a network byte order integer.
    936          """
    937          _word_sep = self.word_sep
    938          if word_sep is not None:
    939              _word_sep = word_sep
    940
    941          _word_fmt = self.word_fmt
    942          if word_fmt is not None:
    943              _word_fmt = word_fmt
    944
    945          words = self.int_to_words(int_val)
    946          tokens = [_word_fmt % i for i in words]
    947          addr = _word_sep.join(tokens)
    948
    949          if self.uppercase:
    950              return addr.upper()
    951          else:
    952              return addr.lower()
    953
    954  #-----------------------------------------------------------------------------
    955  #   Shared strategy objects for supported address types.
    956  #-----------------------------------------------------------------------------
    957
    958  #: A shared strategy object supporting all operations on IPv4 addresses.
    959  ST_IPV4 = None
    960  #   Use the right strategy class dependent upon Python implementation (work-
    961  #   around for various Python bugs).
    962  if USE_IPV4_OPT is True:
    963      ST_IPV4 = IPv4StrategyOpt()
    964  else:
    965      ST_IPV4 = IPv4StrategyStd()
    966
    967  #: A shared strategy object supporting all operations on IPv6 addresses.
    968  ST_IPV6  = IPv6Strategy()
    969  #: A shared strategy object supporting all operations on EUI-48/MAC addresses.
    970  ST_EUI48 = EUI48Strategy()
    971
    972  #: A shared strategy object supporting all operations on EUI-64 addresses.
    973  ST_EUI64 = AddrStrategy(addr_type=AT_EUI64, width=64, word_size=8, \
    974                           word_fmt='%02x', word_sep='-', uppercase=True)
    975
    976  #-----------------------------------------------------------------------------
    977  if __name__ == '__main__':
    978      pass
    979