diff options
author | David Moss <drkjam@gmail.com> | 2015-08-28 23:47:33 +0100 |
---|---|---|
committer | David Moss <drkjam@gmail.com> | 2015-08-28 23:47:33 +0100 |
commit | 5440fdfa45d9c161d4e0682c2145a455debbb4d1 (patch) | |
tree | 975c266d1b171569e0a842b6f357aeaeaf6f064d | |
parent | e151d50dca8c7282a4e25073dc1fd8dcfee93c32 (diff) | |
parent | 77db479fd30543742d2a38be64775bc330d43018 (diff) | |
download | netaddr-5440fdfa45d9c161d4e0682c2145a455debbb4d1.tar.gz |
Merge branch 'braaen-eui' into rel-0.7.x
-rw-r--r-- | netaddr/__init__.py | 3 | ||||
-rw-r--r-- | netaddr/eui/__init__.py | 6 | ||||
-rw-r--r-- | netaddr/strategy/eui48.py | 3 | ||||
-rw-r--r-- | netaddr/strategy/eui64.py | 167 | ||||
-rw-r--r-- | netaddr/tests/eui/test_eui.py | 37 |
5 files changed, 170 insertions, 46 deletions
diff --git a/netaddr/__init__.py b/netaddr/__init__.py index 504535f..bd43eec 100644 --- a/netaddr/__init__.py +++ b/netaddr/__init__.py @@ -42,6 +42,9 @@ from netaddr.strategy.ipv6 import (valid_str as valid_ipv6, ipv6_compact, from netaddr.strategy.eui48 import (mac_eui48, mac_unix, mac_unix_expanded, mac_cisco, mac_bare, mac_pgsql, valid_str as valid_mac) +from netaddr.strategy.eui64 import (eui64_base, eui64_unix, eui64_unix_expanded, + eui64_cisco, eui64_bare, valid_str as valid_mac) + __all__ = [ # Constants. 'ZEROFILL', 'Z', 'INET_PTON', 'P', 'NOHOST', 'N', diff --git a/netaddr/eui/__init__.py b/netaddr/eui/__init__.py index 3affc21..11a050b 100644 --- a/netaddr/eui/__init__.py +++ b/netaddr/eui/__init__.py @@ -11,6 +11,7 @@ identifiers. from netaddr.core import NotRegisteredError, AddrFormatError, DictDotLookup from netaddr.strategy import eui48 as _eui48, eui64 as _eui64 from netaddr.strategy.eui48 import mac_eui48 +from netaddr.strategy.eui64 import eui64_base from netaddr.ip import IPAddress from netaddr.compat import _is_int, _is_str @@ -456,7 +457,10 @@ class EUI(BaseIdentifier): def _set_dialect(self, value): if value is None: - self._dialect = mac_eui48 + if self._module is _eui64: + self._dialect = eui64_base + else: + self._dialect = mac_eui48 else: if hasattr(value, 'word_size') and hasattr(value, 'word_fmt'): self._dialect = value diff --git a/netaddr/strategy/eui48.py b/netaddr/strategy/eui48.py index c960dd6..68c8232 100644 --- a/netaddr/strategy/eui48.py +++ b/netaddr/strategy/eui48.py @@ -19,6 +19,7 @@ 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, @@ -26,8 +27,6 @@ from netaddr.strategy import ( valid_bin as _valid_bin, int_to_bin as _int_to_bin, bin_to_int as _bin_to_int) -from netaddr.compat import _is_str - #: The width (in bits) of this address type. width = 48 diff --git a/netaddr/strategy/eui64.py b/netaddr/strategy/eui64.py index 0100a18..01c98ea 100644 --- a/netaddr/strategy/eui64.py +++ b/netaddr/strategy/eui64.py @@ -9,11 +9,7 @@ IEEE 64-bit EUI (Extended Unique Indentifier) logic. import struct as _struct import re as _re -# This is a fake constant that doesn't really exist. Here for completeness. -AF_EUI64 = 64 - 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, @@ -21,17 +17,12 @@ from netaddr.strategy import ( 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 = 64 -#: 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 = '%.2X' +# This is a fake constant that doesn't really exist. Here for completeness. +AF_EUI64 = 64 -#: The separator character used between each word. -word_sep = '-' +#: The width (in bits) of this address type. +width = 64 #: The AF_* constant value of this address type. family = AF_EUI64 @@ -42,24 +33,88 @@ family_name = 'EUI-64' #: The version of this address type. version = 64 -#: 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 +#----------------------------------------------------------------------------- +# 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 maximum integer value for an individual word in this address type. -max_word = 2 ** word_size - 1 + #: The number base to be used when interpreting word values as integers. + word_base = 16 -#: Compiled regular expression for detecting value EUI-64 identifiers. -RE_EUI64_FORMATS = [ - _re.compile('^' + ':'.join(['([0-9A-F]{1,2})'] * 8) + '$', _re.IGNORECASE), - _re.compile('^' + '-'.join(['([0-9A-F]{1,2})'] * 8) + '$', _re.IGNORECASE), - _re.compile('^(' + '[0-9A-F]' * 16 + ')$', _re.IGNORECASE), -] + +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) + '$', + '^' + '\.'.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): @@ -89,7 +144,7 @@ def str_to_int(addr): :param addr: An IEEE EUI-64 indentifier in string form. :return: An unsigned integer that is equivalent to value represented - by EUI-64 string identifier. + by EUI-64 string address formatted according to the dialect """ words = [] @@ -100,13 +155,25 @@ def str_to_int(addr): except TypeError: raise AddrFormatError('invalid IEEE EUI-64 identifier: %r!' % addr) - if _is_str(words): - return int(words, 16) - if len(words) != num_words: + 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(''.join(['%.2x' % int(w, 16) for w in words]), 16) + return int_val def int_to_str(int_val, dialect=None): @@ -114,13 +181,14 @@ def int_to_str(int_val, dialect=None): :param int_val: An unsigned integer. :param dialect: (optional) a Python class defining formatting options - (Please Note - not currently in use). :return: An IEEE EUI-64 identifier that is equivalent to unsigned integer. """ - words = int_to_words(int_val) - tokens = [word_fmt % i for i in words] - addr = word_sep.join(tokens) + 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 @@ -155,30 +223,45 @@ def packed_to_int(packed_int): def valid_words(words, dialect=None): - return _valid_words(words, word_size, num_words) + 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): - return _int_to_words(int_val, word_size, num_words) + 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): - return _words_to_int(words, word_size, num_words) + 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): - return _valid_bits(bits, width, word_sep) + if dialect is None: + dialect = DEFAULT_EUI64_DIALECT + return _valid_bits(bits, width, dialect.word_sep) def bits_to_int(bits, dialect=None): - return _bits_to_int(bits, width, word_sep) + 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): - return _int_to_bits(int_val, word_size, num_words, word_sep) + 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): +def valid_bin(bin_val, dialect=None): + if dialect is None: + dialect = DEFAULT_EUI64_DIALECT return _valid_bin(bin_val, width) diff --git a/netaddr/tests/eui/test_eui.py b/netaddr/tests/eui/test_eui.py index 48ef7d2..c806028 100644 --- a/netaddr/tests/eui/test_eui.py +++ b/netaddr/tests/eui/test_eui.py @@ -4,7 +4,9 @@ import random import pytest -from netaddr import EUI, mac_unix, mac_unix_expanded, mac_cisco, mac_bare, mac_pgsql, OUI, IAB, IPAddress +from netaddr import (EUI, mac_unix, mac_unix_expanded, mac_cisco, + mac_bare, mac_pgsql, eui64_unix, eui64_unix_expanded, + eui64_cisco, eui64_bare, OUI, IAB, IPAddress) def test_mac_address_properties(): @@ -99,6 +101,39 @@ def test_eui_custom_dialect(): assert str(mac) == '00:1B:77:49:54:FD' +def test_eui64_dialects(): + mac = EUI('00-1B-77-49-54-FD-12-34') + assert str(mac) == '00-1B-77-49-54-FD-12-34' + + mac = EUI('00-1B-77-49-54-FD-12-34', dialect=eui64_unix) + assert str(mac) == '0:1b:77:49:54:fd:12:34' + + mac = EUI('00-1B-77-49-54-FD-12-34', dialect=eui64_unix_expanded) + assert str(mac) == '00:1b:77:49:54:fd:12:34' + + mac = EUI('00-1B-77-49-54-FD-12-34', dialect=eui64_cisco) + assert str(mac) == '001b.7749.54fd.1234' + + mac = EUI('00-1B-77-49-54-FD-12-34', dialect=eui64_bare) + assert str(mac) == '001B774954FD1234' + + +def test_eui64_dialect_property_assignment(): + mac = EUI('00-1B-77-49-54-FD-12-34') + assert str(mac) == '00-1B-77-49-54-FD-12-34' + + mac.dialect = eui64_cisco + assert str(mac) == '001b.7749.54fd.1234' + + +def test_eui64_custom_dialect(): + class eui64_custom(eui64_unix): + word_fmt = '%.2X' + + mac = EUI('00-1B-77-49-54-FD-12-34', dialect=eui64_custom) + assert str(mac) == '00:1B:77:49:54:FD:12:34' + + def test_eui_oui_information(): mac = EUI('00-1B-77-49-54-FD') oui = mac.oui |