summaryrefslogtreecommitdiffstats
path: root/netaddr/strategy
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--netaddr/strategy/__init__.py273
-rw-r--r--netaddr/strategy/eui48.py296
-rw-r--r--netaddr/strategy/eui64.py273
-rw-r--r--netaddr/strategy/ipv4.py283
-rw-r--r--netaddr/strategy/ipv6.py259
5 files changed, 1384 insertions, 0 deletions
diff --git a/netaddr/strategy/__init__.py b/netaddr/strategy/__init__.py
new file mode 100644
index 0000000..7b5861d
--- /dev/null
+++ b/netaddr/strategy/__init__.py
@@ -0,0 +1,273 @@
+#-----------------------------------------------------------------------------
+# Copyright (c) 2008 by David P. D. Moss. All rights reserved.
+#
+# Released under the BSD license. See the LICENSE file for details.
+#-----------------------------------------------------------------------------
+"""
+Shared logic for various address types.
+"""
+import re as _re
+
+from netaddr.compat import _range, _is_str
+
+
+def bytes_to_bits():
+ """
+ :return: A 256 element list containing 8-bit binary digit strings. The
+ list index value is equivalent to its bit string value.
+ """
+ lookup = []
+ bits_per_byte = _range(7, -1, -1)
+ for num in range(256):
+ bits = 8 * [None]
+ for i in bits_per_byte:
+ bits[i] = '01'[num & 1]
+ num >>= 1
+ lookup.append(''.join(bits))
+ return lookup
+
+#: A lookup table of 8-bit integer values to their binary digit bit strings.
+BYTES_TO_BITS = bytes_to_bits()
+
+
+def valid_words(words, word_size, num_words):
+ """
+ :param words: A sequence of unsigned integer word values.
+
+ :param word_size: Width (in bits) of each unsigned integer word value.
+
+ :param num_words: Number of unsigned integer words expected.
+
+ :return: ``True`` if word sequence is valid for this address type,
+ ``False`` otherwise.
+ """
+ if not hasattr(words, '__iter__'):
+ return False
+
+ if len(words) != num_words:
+ return False
+
+ max_word = 2 ** word_size - 1
+
+ for i in words:
+ if not 0 <= i <= max_word:
+ return False
+
+ return True
+
+
+def int_to_words(int_val, word_size, num_words):
+ """
+ :param int_val: Unsigned integer to be divided into words of equal size.
+
+ :param word_size: Width (in bits) of each unsigned integer word value.
+
+ :param num_words: Number of unsigned integer words expected.
+
+ :return: A tuple contain unsigned integer word values split according
+ to provided arguments.
+ """
+ max_int = 2 ** (num_words * word_size) - 1
+
+ if not 0 <= int_val <= max_int:
+ raise IndexError('integer out of bounds: %r!' % hex(int_val))
+
+ max_word = 2 ** word_size - 1
+
+ words = []
+ for _ in range(num_words):
+ word = int_val & max_word
+ words.append(int(word))
+ int_val >>= word_size
+
+ return tuple(reversed(words))
+
+
+def words_to_int(words, word_size, num_words):
+ """
+ :param words: A sequence of unsigned integer word values.
+
+ :param word_size: Width (in bits) of each unsigned integer word value.
+
+ :param num_words: Number of unsigned integer words expected.
+
+ :return: An unsigned integer that is equivalent to value represented
+ by word sequence.
+ """
+ if not valid_words(words, word_size, num_words):
+ raise ValueError('invalid integer word sequence: %r!' % (words,))
+
+ int_val = 0
+ for i, num in enumerate(reversed(words)):
+ word = num
+ word = word << word_size * i
+ int_val = int_val | word
+
+ return int_val
+
+
+def valid_bits(bits, width, word_sep=''):
+ """
+ :param bits: A network address in a delimited binary string format.
+
+ :param width: Maximum width (in bits) of a network address (excluding
+ delimiters).
+
+ :param word_sep: (optional) character or string used to delimit word
+ groups (default: '', no separator).
+
+ :return: ``True`` if network address is valid, ``False`` otherwise.
+ """
+ if not _is_str(bits):
+ return False
+
+ if word_sep != '':
+ bits = bits.replace(word_sep, '')
+
+ if len(bits) != width:
+ return False
+
+ max_int = 2 ** width - 1
+
+ try:
+ if 0 <= int(bits, 2) <= max_int:
+ return True
+ except ValueError:
+ pass
+
+ return False
+
+
+def bits_to_int(bits, width, word_sep=''):
+ """
+ :param bits: A network address in a delimited binary string format.
+
+ :param width: Maximum width (in bits) of a network address (excluding
+ delimiters).
+
+ :param word_sep: (optional) character or string used to delimit word
+ groups (default: '', no separator).
+
+ :return: An unsigned integer that is equivalent to value represented
+ by network address in readable binary form.
+ """
+ if not valid_bits(bits, width, word_sep):
+ raise ValueError('invalid readable binary string: %r!' % (bits,))
+
+ if word_sep != '':
+ bits = bits.replace(word_sep, '')
+
+ return int(bits, 2)
+
+
+def int_to_bits(int_val, word_size, num_words, word_sep=''):
+ """
+ :param int_val: An unsigned integer.
+
+ :param word_size: Width (in bits) of each unsigned integer word value.
+
+ :param num_words: Number of unsigned integer words expected.
+
+ :param word_sep: (optional) character or string used to delimit word
+ groups (default: '', no separator).
+
+ :return: A network address in a delimited binary string format that is
+ equivalent in value to unsigned integer.
+ """
+ bit_words = []
+
+ for word in int_to_words(int_val, word_size, num_words):
+ bits = []
+ while word:
+ bits.append(BYTES_TO_BITS[word & 255])
+ word >>= 8
+ bits.reverse()
+ bit_str = ''.join(bits) or '0' * word_size
+ bits = ('0' * word_size + bit_str)[-word_size:]
+ bit_words.append(bits)
+
+ if word_sep != '':
+ # Check custom separator.
+ if not _is_str(word_sep):
+ raise ValueError('word separator is not a string: %r!' % (word_sep,))
+
+ return word_sep.join(bit_words)
+
+
+def valid_bin(bin_val, width):
+ """
+ :param bin_val: A network address in Python's binary representation format
+ ('0bxxx').
+
+ :param width: Maximum width (in bits) of a network address (excluding
+ delimiters).
+
+ :return: ``True`` if network address is valid, ``False`` otherwise.
+ """
+ if not _is_str(bin_val):
+ return False
+
+ if not bin_val.startswith('0b'):
+ return False
+
+ bin_val = bin_val.replace('0b', '')
+
+ if len(bin_val) > width:
+ return False
+
+ max_int = 2 ** width - 1
+
+ try:
+ if 0 <= int(bin_val, 2) <= max_int:
+ return True
+ except ValueError:
+ pass
+
+ return False
+
+
+def int_to_bin(int_val, width):
+ """
+ :param int_val: An unsigned integer.
+
+ :param width: Maximum allowed width (in bits) of a unsigned integer.
+
+ :return: Equivalent string value in Python's binary representation format
+ ('0bxxx').
+ """
+ bin_tokens = []
+
+ try:
+ # Python 2.6.x and upwards.
+ bin_val = bin(int_val)
+ except NameError:
+ # Python 2.4.x and 2.5.x
+ i = int_val
+ while i > 0:
+ word = i & 0xff
+ bin_tokens.append(BYTES_TO_BITS[word])
+ i >>= 8
+
+ bin_tokens.reverse()
+ bin_val = '0b' + _re.sub(r'^[0]+([01]+)$', r'\1', ''.join(bin_tokens))
+
+ if len(bin_val[2:]) > width:
+ raise IndexError('binary string out of bounds: %s!' % (bin_val,))
+
+ return bin_val
+
+
+def bin_to_int(bin_val, width):
+ """
+ :param bin_val: A string containing an unsigned integer in Python's binary
+ representation format ('0bxxx').
+
+ :param width: Maximum allowed width (in bits) of a unsigned integer.
+
+ :return: An unsigned integer that is equivalent to value represented
+ by Python binary string format.
+ """
+ if not valid_bin(bin_val, width):
+ raise ValueError('not a valid Python binary string: %r!' % (bin_val,))
+
+ return int(bin_val.replace('0b', ''), 2)
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)
diff --git a/netaddr/strategy/eui64.py b/netaddr/strategy/eui64.py
new file mode 100644
index 0000000..9b57ee3
--- /dev/null
+++ b/netaddr/strategy/eui64.py
@@ -0,0 +1,273 @@
+#-----------------------------------------------------------------------------
+# Copyright (c) 2008 by David P. D. Moss. All rights reserved.
+#
+# Released under the BSD license. See the LICENSE file for details.
+#-----------------------------------------------------------------------------
+"""
+IEEE 64-bit EUI (Extended Unique Identifier) logic.
+"""
+import struct as _struct
+import re as _re
+
+from netaddr.core import AddrFormatError
+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)
+
+
+# This is a fake constant that doesn't really exist. Here for completeness.
+AF_EUI64 = 64
+
+#: The width (in bits) of this address type.
+width = 64
+
+#: The AF_* constant value of this address type.
+family = AF_EUI64
+
+#: A friendly string name address type.
+family_name = 'EUI-64'
+
+#: The version of this address type.
+version = 64
+
+#: The maximum integer value that can be represented by this address type.
+max_int = 2 ** width - 1
+
+#-----------------------------------------------------------------------------
+# Dialect classes.
+#-----------------------------------------------------------------------------
+
+class eui64_base(object):
+ """A standard IEEE EUI-64 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 eui64_unix(eui64_base):
+ """A UNIX-style MAC address dialect class."""
+ word_size = 8
+ num_words = width // word_size
+ word_sep = ':'
+ word_fmt = '%x'
+ word_base = 16
+
+
+class eui64_unix_expanded(eui64_unix):
+ """A UNIX-style MAC address dialect class with leading zeroes."""
+ word_fmt = '%.2x'
+
+
+class eui64_cisco(eui64_base):
+ """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 eui64_bare(eui64_base):
+ """A bare (no delimiters) MAC address dialect class."""
+ word_size = 64
+ num_words = width // word_size
+ word_sep = ''
+ word_fmt = '%.16X'
+ word_base = 16
+
+
+#: The default dialect to be used when not specified by the user.
+
+DEFAULT_EUI64_DIALECT = eui64_base
+
+#-----------------------------------------------------------------------------
+#: Regular expressions to match all supported MAC address formats.
+RE_EUI64_FORMATS = (
+ # 2 bytes x 8 (UNIX, Windows, EUI-64)
+ '^' + ':'.join(['([0-9A-F]{1,2})'] * 8) + '$',
+ '^' + '-'.join(['([0-9A-F]{1,2})'] * 8) + '$',
+
+ # 4 bytes x 4 (Cisco like)
+ '^' + ':'.join(['([0-9A-F]{1,4})'] * 4) + '$',
+ '^' + '-'.join(['([0-9A-F]{1,4})'] * 4) + '$',
+ '^' + r'\.'.join(['([0-9A-F]{1,4})'] * 4) + '$',
+
+ # 16 bytes (bare, no delimiters)
+ '^(' + ''.join(['[0-9A-F]'] * 16) + ')$',
+)
+# For efficiency, each string regexp converted in place to its compiled
+# counterpart.
+RE_EUI64_FORMATS = [_re.compile(_, _re.IGNORECASE) for _ in RE_EUI64_FORMATS]
+
+
+def _get_match_result(address, formats):
+ for regexp in formats:
+ match = regexp.findall(address)
+ if match:
+ return match[0]
+
+
+def valid_str(addr):
+ """
+ :param addr: An IEEE EUI-64 identifier in string form.
+
+ :return: ``True`` if EUI-64 identifier is valid, ``False`` otherwise.
+ """
+ try:
+ if _get_match_result(addr, RE_EUI64_FORMATS):
+ return True
+ except TypeError:
+ pass
+
+ return False
+
+
+def str_to_int(addr):
+ """
+ :param addr: An IEEE EUI-64 identifier in string form.
+
+ :return: An unsigned integer that is equivalent to value represented
+ by EUI-64 string address formatted according to the dialect
+ """
+ words = []
+
+ try:
+ words = _get_match_result(addr, RE_EUI64_FORMATS)
+ if not words:
+ raise TypeError
+ except TypeError:
+ raise AddrFormatError('invalid IEEE EUI-64 identifier: %r!' % (addr,))
+
+ if isinstance(words, tuple):
+ pass
+ else:
+ words = (words,)
+
+ if len(words) == 8:
+ # 2 bytes x 8 (UNIX, Windows, EUI-48)
+ int_val = int(''.join(['%.2x' % int(w, 16) for w in words]), 16)
+ elif len(words) == 4:
+ # 4 bytes x 4 (Cisco like)
+ int_val = int(''.join(['%.4x' % int(w, 16) for w in words]), 16)
+ elif len(words) == 1:
+ # 16 bytes (bare, no delimiters)
+ int_val = int('%016x' % int(words[0], 16), 16)
+ else:
+ raise AddrFormatError(
+ 'bad word count for EUI-64 identifier: %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-64 identifier that is equivalent to unsigned integer.
+ """
+ if dialect is None:
+ dialect = eui64_base
+ 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.
+ """
+ words = int_to_words(int_val)
+ return _struct.pack('>8B', *words)
+
+
+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('>8B', 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_EUI64_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_EUI64_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_EUI64_DIALECT
+ return _words_to_int(words, dialect.word_size, dialect.num_words)
+
+
+def valid_bits(bits, dialect=None):
+ if dialect is None:
+ dialect = DEFAULT_EUI64_DIALECT
+ return _valid_bits(bits, width, dialect.word_sep)
+
+
+def bits_to_int(bits, dialect=None):
+ if dialect is None:
+ dialect = DEFAULT_EUI64_DIALECT
+ return _bits_to_int(bits, width, dialect.word_sep)
+
+
+def int_to_bits(int_val, dialect=None):
+ if dialect is None:
+ dialect = DEFAULT_EUI64_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_EUI64_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)
diff --git a/netaddr/strategy/ipv4.py b/netaddr/strategy/ipv4.py
new file mode 100644
index 0000000..f42fa07
--- /dev/null
+++ b/netaddr/strategy/ipv4.py
@@ -0,0 +1,283 @@
+#-----------------------------------------------------------------------------
+# Copyright (c) 2008 by David P. D. Moss. All rights reserved.
+#
+# Released under the BSD license. See the LICENSE file for details.
+#-----------------------------------------------------------------------------
+"""IPv4 address logic."""
+
+import sys as _sys
+import struct as _struct
+
+from socket import inet_aton as _inet_aton
+# Check whether we need to use fallback code or not.
+if _sys.platform in ('win32', 'cygwin'):
+ # inet_pton() not available on Windows. inet_pton() under cygwin
+ # behaves exactly like inet_aton() and is therefore highly unreliable.
+ from netaddr.fbsocket import inet_pton as _inet_pton, AF_INET
+else:
+ # All other cases, use all functions from the socket module.
+ from socket import inet_pton as _inet_pton, AF_INET
+
+from netaddr.core import AddrFormatError, ZEROFILL, INET_PTON
+
+from netaddr.strategy import (
+ valid_words as _valid_words, 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)
+
+from netaddr.compat import _str_type
+
+#: The width (in bits) of this address type.
+width = 32
+
+#: The individual word size (in bits) of this address type.
+word_size = 8
+
+#: The format string to be used when converting words to string values.
+word_fmt = '%d'
+
+#: The separator character used between each word.
+word_sep = '.'
+
+#: The AF_* constant value of this address type.
+family = AF_INET
+
+#: A friendly string name address type.
+family_name = 'IPv4'
+
+#: The version of this address type.
+version = 4
+
+#: The number base to be used when interpreting word values as integers.
+word_base = 10
+
+#: The maximum integer value that can be represented by this address type.
+max_int = 2 ** width - 1
+
+#: 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
+
+#: A dictionary mapping IPv4 CIDR prefixes to the equivalent netmasks.
+prefix_to_netmask = dict(
+ [(i, max_int ^ (2 ** (width - i) - 1)) for i in range(0, width + 1)])
+
+#: A dictionary mapping IPv4 netmasks to their equivalent CIDR prefixes.
+netmask_to_prefix = dict(
+ [(max_int ^ (2 ** (width - i) - 1), i) for i in range(0, width + 1)])
+
+#: A dictionary mapping IPv4 CIDR prefixes to the equivalent hostmasks.
+prefix_to_hostmask = dict(
+ [(i, (2 ** (width - i) - 1)) for i in range(0, width + 1)])
+
+#: A dictionary mapping IPv4 hostmasks to their equivalent CIDR prefixes.
+hostmask_to_prefix = dict(
+ [((2 ** (width - i) - 1), i) for i in range(0, width + 1)])
+
+
+def valid_str(addr, flags=0):
+ """
+ :param addr: An IPv4 address in presentation (string) format.
+
+ :param flags: decides which rules are applied to the interpretation of the
+ addr value. Supported constants are INET_PTON and ZEROFILL. See the
+ :class:`IPAddress` documentation for details.
+
+ .. versionchanged:: 0.10.1
+ ``flags`` is scheduled to default to :data:`INET_PTON` instead of :data:`INET_ATON`
+ in the future.
+
+ :return: ``True`` if IPv4 address is valid, ``False`` otherwise.
+ """
+ if addr == '':
+ raise AddrFormatError('Empty strings are not supported!')
+
+ validity = True
+
+ if flags & ZEROFILL:
+ addr = '.'.join(['%d' % int(i) for i in addr.split('.')])
+
+ try:
+ if flags & INET_PTON:
+ _inet_pton(AF_INET, addr)
+ else:
+ _inet_aton(addr)
+ except Exception:
+ validity = False
+
+ return validity
+
+
+def str_to_int(addr, flags=0):
+ """
+ :param addr: An IPv4 dotted decimal address in string form.
+
+ :param flags: decides which rules are applied to the interpretation of the
+ addr value. Supported constants are INET_PTON and ZEROFILL. See the
+ :class:`IPAddress` documentation for details.
+
+ :return: The equivalent unsigned integer for a given IPv4 address.
+ """
+ if flags & ZEROFILL:
+ addr = '.'.join(['%d' % int(i) for i in addr.split('.')])
+
+ try:
+ if flags & INET_PTON:
+ return _struct.unpack('>I', _inet_pton(AF_INET, addr))[0]
+ else:
+ return _struct.unpack('>I', _inet_aton(addr))[0]
+ except Exception:
+ raise AddrFormatError('%r is not a valid IPv4 address string!' % (addr,))
+
+
+def int_to_str(int_val, dialect=None):
+ """
+ :param int_val: An unsigned integer.
+
+ :param dialect: (unused) Any value passed in is ignored.
+
+ :return: The IPv4 presentation (string) format address equivalent to the
+ unsigned integer provided.
+ """
+ if 0 <= int_val <= max_int:
+ return '%d.%d.%d.%d' % (
+ int_val >> 24,
+ (int_val >> 16) & 0xff,
+ (int_val >> 8) & 0xff,
+ int_val & 0xff)
+ else:
+ raise ValueError('%r is not a valid 32-bit unsigned integer!' % (int_val,))
+
+
+def int_to_arpa(int_val):
+ """
+ :param int_val: An unsigned integer.
+
+ :return: The reverse DNS lookup for an IPv4 address in network byte
+ order integer form.
+ """
+ words = ["%d" % i for i in int_to_words(int_val)]
+ words.reverse()
+ words.extend(['in-addr', 'arpa', ''])
+ return '.'.join(words)
+
+
+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('>I', int_val)
+
+
+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.
+ """
+ return _struct.unpack('>I', packed_int)[0]
+
+
+def valid_words(words):
+ return _valid_words(words, word_size, num_words)
+
+
+def int_to_words(int_val):
+ """
+ :param int_val: An unsigned integer.
+
+ :return: An integer word (octet) sequence that is equivalent to value
+ represented by an unsigned integer.
+ """
+ if not 0 <= int_val <= max_int:
+ raise ValueError('%r is not a valid integer value supported by'
+ 'this address type!' % (int_val,))
+ return ( int_val >> 24,
+ (int_val >> 16) & 0xff,
+ (int_val >> 8) & 0xff,
+ int_val & 0xff)
+
+
+def words_to_int(words):
+ """
+ :param words: A list or tuple containing integer octets.
+
+ :return: An unsigned integer that is equivalent to value represented
+ by word (octet) sequence.
+ """
+ if not valid_words(words):
+ raise ValueError('%r is not a valid octet list for an IPv4 address!' % (words,))
+ return _struct.unpack('>I', _struct.pack('4B', *words))[0]
+
+
+def valid_bits(bits):
+ return _valid_bits(bits, width, word_sep)
+
+
+def bits_to_int(bits):
+ return _bits_to_int(bits, width, word_sep)
+
+
+def int_to_bits(int_val, word_sep=None):
+ if word_sep is None:
+ word_sep = globals()['word_sep']
+ return _int_to_bits(int_val, word_size, num_words, word_sep)
+
+
+def valid_bin(bin_val):
+ 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)
+
+
+def expand_partial_address(addr):
+ """
+ Expands a partial IPv4 address into a full 4-octet version.
+
+ :param addr: an partial or abbreviated IPv4 address
+
+ :return: an expanded IP address in presentation format (x.x.x.x)
+
+ """
+ tokens = []
+
+ error = AddrFormatError('invalid partial IPv4 address: %r!' % addr)
+
+ if isinstance(addr, _str_type):
+ if ':' in addr:
+ # Ignore IPv6 ...
+ raise error
+
+ try:
+ if '.' in addr:
+ tokens = ['%d' % int(o) for o in addr.split('.')]
+ else:
+ tokens = ['%d' % int(addr)]
+ except ValueError:
+ raise error
+
+ if 1 <= len(tokens) <= 4:
+ for i in range(4 - len(tokens)):
+ tokens.append('0')
+ else:
+ raise error
+
+ if not tokens:
+ raise error
+
+ return '%s.%s.%s.%s' % tuple(tokens)
+
diff --git a/netaddr/strategy/ipv6.py b/netaddr/strategy/ipv6.py
new file mode 100644
index 0000000..de2a935
--- /dev/null
+++ b/netaddr/strategy/ipv6.py
@@ -0,0 +1,259 @@
+#-----------------------------------------------------------------------------
+# Copyright (c) 2008 by David P. D. Moss. All rights reserved.
+#
+# Released under the BSD license. See the LICENSE file for details.
+#-----------------------------------------------------------------------------
+"""
+IPv6 address logic.
+"""
+import struct as _struct
+
+OPT_IMPORTS = False
+
+# Check whether we need to use fallback code or not.
+try:
+ import socket as _socket
+ # These might all generate exceptions on different platforms.
+ if not _socket.has_ipv6:
+ raise Exception('IPv6 disabled')
+ _socket.inet_pton
+ _socket.AF_INET6
+ from _socket import (inet_pton as _inet_pton, inet_ntop as _inet_ntop,
+ AF_INET6)
+ OPT_IMPORTS = True
+except Exception:
+ from netaddr.fbsocket import (inet_pton as _inet_pton, inet_ntop as _inet_ntop,
+ AF_INET6)
+
+from netaddr.core import AddrFormatError
+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 = 128
+
+#: The individual word size (in bits) of this address type.
+word_size = 16
+
+#: The separator character used between each word.
+word_sep = ':'
+
+#: The AF_* constant value of this address type.
+family = AF_INET6
+
+#: A friendly string name address type.
+family_name = 'IPv6'
+
+#: The version of this address type.
+version = 6
+
+#: The number base to be used when interpreting word values as integers.
+word_base = 16
+
+#: The maximum integer value that can be represented by this address type.
+max_int = 2 ** width - 1
+
+#: 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
+
+#: A dictionary mapping IPv6 CIDR prefixes to the equivalent netmasks.
+prefix_to_netmask = dict(
+ [(i, max_int ^ (2 ** (width - i) - 1)) for i in range(0, width+1)])
+
+#: A dictionary mapping IPv6 netmasks to their equivalent CIDR prefixes.
+netmask_to_prefix = dict(
+ [(max_int ^ (2 ** (width - i) - 1), i) for i in range(0, width+1)])
+
+#: A dictionary mapping IPv6 CIDR prefixes to the equivalent hostmasks.
+prefix_to_hostmask = dict(
+ [(i, (2 ** (width - i) - 1)) for i in range(0, width+1)])
+
+#: A dictionary mapping IPv6 hostmasks to their equivalent CIDR prefixes.
+hostmask_to_prefix = dict(
+ [((2 ** (width - i) - 1), i) for i in range(0, width+1)])
+
+#-----------------------------------------------------------------------------
+# Dialect classes.
+#-----------------------------------------------------------------------------
+
+class ipv6_compact(object):
+ """An IPv6 dialect class - compact form."""
+ #: The format string used to converting words into string values.
+ word_fmt = '%x'
+
+ #: Boolean flag indicating if IPv6 compaction algorithm should be used.
+ compact = True
+
+class ipv6_full(ipv6_compact):
+ """An IPv6 dialect class - 'all zeroes' form."""
+
+ #: Boolean flag indicating if IPv6 compaction algorithm should be used.
+ compact = False
+
+class ipv6_verbose(ipv6_compact):
+ """An IPv6 dialect class - extra wide 'all zeroes' form."""
+
+ #: The format string used to converting words into string values.
+ word_fmt = '%.4x'
+
+ #: Boolean flag indicating if IPv6 compaction algorithm should be used.
+ compact = False
+
+
+def valid_str(addr, flags=0):
+ """
+ :param addr: An IPv6 address in presentation (string) format.
+
+ :param flags: decides which rules are applied to the interpretation of the
+ addr value. Future use - currently has no effect.
+
+ :return: ``True`` if IPv6 address is valid, ``False`` otherwise.
+ """
+ if addr == '':
+ raise AddrFormatError('Empty strings are not supported!')
+
+ try:
+ _inet_pton(AF_INET6, addr)
+ except:
+ return False
+ return True
+
+
+def str_to_int(addr, flags=0):
+ """
+ :param addr: An IPv6 address in string form.
+
+ :param flags: decides which rules are applied to the interpretation of the
+ addr value. Future use - currently has no effect.
+
+ :return: The equivalent unsigned integer for a given IPv6 address.
+ """
+ try:
+ packed_int = _inet_pton(AF_INET6, addr)
+ return packed_to_int(packed_int)
+ except Exception:
+ raise AddrFormatError('%r is not a valid IPv6 address string!' % (addr,))
+
+
+def int_to_str(int_val, dialect=None):
+ """
+ :param int_val: An unsigned integer.
+
+ :param dialect: (optional) a Python class defining formatting options.
+
+ :return: The IPv6 presentation (string) format address equivalent to the
+ unsigned integer provided.
+ """
+ if dialect is None:
+ dialect = ipv6_compact
+
+ addr = None
+
+ try:
+ packed_int = int_to_packed(int_val)
+ if dialect.compact:
+ # Default return value.
+ addr = _inet_ntop(AF_INET6, packed_int)
+ else:
+ # Custom return value.
+ words = list(_struct.unpack('>8H', packed_int))
+ tokens = [dialect.word_fmt % word for word in words]
+ addr = word_sep.join(tokens)
+ except Exception:
+ raise ValueError('%r is not a valid 128-bit unsigned integer!' % (int_val,))
+
+ return addr
+
+
+def int_to_arpa(int_val):
+ """
+ :param int_val: An unsigned integer.
+
+ :return: The reverse DNS lookup for an IPv6 address in network byte
+ order integer form.
+ """
+ addr = int_to_str(int_val, ipv6_verbose)
+ tokens = list(addr.replace(':', ''))
+ tokens.reverse()
+ # We won't support ip6.int here - see RFC 3152 for details.
+ tokens = tokens + ['ip6', 'arpa', '']
+ return '.'.join(tokens)
+
+
+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.
+ """
+ words = int_to_words(int_val, 4, 32)
+ return _struct.pack('>4I', *words)
+
+
+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('>4I', packed_int))
+
+ int_val = 0
+ for i, num in enumerate(reversed(words)):
+ word = num
+ word = word << 32 * i
+ int_val = int_val | word
+
+ return int_val
+
+
+def valid_words(words):
+ return _valid_words(words, word_size, num_words)
+
+
+def int_to_words(int_val, num_words=None, word_size=None):
+ if num_words is None:
+ num_words = globals()['num_words']
+ if word_size is None:
+ word_size = globals()['word_size']
+ return _int_to_words(int_val, word_size, num_words)
+
+
+def words_to_int(words):
+ return _words_to_int(words, word_size, num_words)
+
+
+def valid_bits(bits):
+ return _valid_bits(bits, width, word_sep)
+
+
+def bits_to_int(bits):
+ return _bits_to_int(bits, width, word_sep)
+
+
+def int_to_bits(int_val, word_sep=None):
+ if word_sep is None:
+ word_sep = globals()['word_sep']
+ return _int_to_bits(int_val, word_size, num_words, word_sep)
+
+
+def valid_bin(bin_val):
+ 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)