summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Moss <drkjam@gmail.com>2015-08-28 23:47:33 +0100
committerDavid Moss <drkjam@gmail.com>2015-08-28 23:47:33 +0100
commit5440fdfa45d9c161d4e0682c2145a455debbb4d1 (patch)
tree975c266d1b171569e0a842b6f357aeaeaf6f064d
parente151d50dca8c7282a4e25073dc1fd8dcfee93c32 (diff)
parent77db479fd30543742d2a38be64775bc330d43018 (diff)
downloadnetaddr-5440fdfa45d9c161d4e0682c2145a455debbb4d1.tar.gz
Merge branch 'braaen-eui' into rel-0.7.x
-rw-r--r--netaddr/__init__.py3
-rw-r--r--netaddr/eui/__init__.py6
-rw-r--r--netaddr/strategy/eui48.py3
-rw-r--r--netaddr/strategy/eui64.py167
-rw-r--r--netaddr/tests/eui/test_eui.py37
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