Commits

buge  committed e203eaf

Added complete ipv6 address text representation support according to RFC 5952.

  • Participants
  • Parent commits fe739a9

Comments (0)

Files changed (2)

File addrinfo/addresses.py

 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 """
-    Addresses
-    ---------
+    addrinfo.addresses
+    ------------------
 
     TODO add documentation
 """
 
         # Parse if string value
         if isinstance(address, basestring):
-            a = self.__class__.PARSER(address)
+            a = self.__class__._PARSER(address)
         else:
             a = int(address)
 
 
 class _AddressFormatter(object):
 
-    def __init__(self, address_size, group_size, group_format, separator):
+    def __init__(self, address_size, group_size, group_format, separator,
+            compress=False):
         self.__address_size = address_size
         self.__group_size = group_size
         self.__group_format = group_format
         self.__separator = separator
+        self.__compress = compress
 
     def get_address_size(self):
         return self.__address_size
     def get_separator(self):
         return self.__separator
 
+    def is_compressing(self):
+        return self.__compress
+
     def format(self, address):
         a = address
         if isinstance(a, Address):
         group_format = self.get_group_format()
         mask = 2 ** group_size - 1
 
-        fields = []
-        for i in xrange(0, address_size / group_size):
-            fields.insert(0, group_format % (a & mask))
+        groups = []
+        compress_s = 0
+        compress_l = 0
+        curr_s = 0
+        curr_l = 0
+        for i in xrange(address_size / group_size - 1, -1, -1):
+            group = a & mask
+
+            # Keep track of the longest range of consecutive zero-groups in case
+            # we need to callapse them (as in IPv6, for example).
+            if group == 0:
+                curr_s = i
+                curr_l += 1
+            elif curr_l > 0:
+                if curr_l >= compress_l:
+                    compress_s = curr_s
+                    compress_l = curr_l
+                curr_l = 0
+
+            groups.insert(0, group_format % group)
             a >>= group_size
-        return self.get_separator().join(fields)
+
+        if curr_l > 0:
+            if curr_l >= compress_l:
+                compress_s = curr_s
+                compress_l = curr_l
+            curr_l = 0
+
+        # Collapse consecutive zero groups if required
+        if self.is_compressing() and compress_l > 0:
+            groups = groups[:compress_s] + [''] + groups[compress_s + compress_l:]
+            if compress_s == 0:
+                groups.insert(0, '')
+            if compress_s + compress_l == address_size / group_size:
+                groups.append('')
+
+        return self.get_separator().join(groups)
 
 
 class EUIAddress(Address):
 class EUI48Address(EUIAddress):
 
     BIT_SIZE = 48
-    PARSER = staticmethod(parser.parse_eui)
+    _PARSER = staticmethod(parser.parse_eui)
 
     @classmethod
     def value_of(cls, value):
 class EUI64Address(EUIAddress):
 
     BIT_SIZE = 64
-    PARSER = staticmethod(parser.parse_eui)
+    _PARSER = staticmethod(parser.parse_eui)
 
     @classmethod
     def value_of(cls, value):
 class IPv4Address(IPAddress):
 
     BIT_SIZE = 32
-    PARSER = staticmethod(parser.parse_ipv4)
+    _PARSER = staticmethod(parser.parse_ipv4)
 
     @classmethod
     def value_of(cls, value):
 class IPv6Address(IPAddress):
 
     BIT_SIZE = 128
-    PARSER = staticmethod(parser.parse_ipv6)
+    _PARSER = staticmethod(parser.parse_ipv6)
 
-    # Descriptions as defined in RFC 4291
+    # Descriptions as defined in RFC 4291, section 2.4
     TYPE_UNSPECIFIED = 'Unspecified'
     TYPE_LOOPBACK = 'Loopback'
     TYPE_MULTICAST = 'Multicast'
             return IPv6Address.TYPE_UNSPECIFIED
         if self.get_address() == 1:
             return IPv6Address.TYPE_LOOPBACK
-        if self._has_prefix(255, 8):
+        if self._has_prefix(0b11111111, 8):
             return IPv6Address.TYPE_MULTICAST
-        if self._has_prefix(1018, 10):
+        if self._has_prefix(0b1111111010, 10):
             return IPv6Address.TYPE_LINK_LOCAL_UNICAST
         return IPv6Address.TYPE_GLOBAL_UNICAST
 
     def is_ipv4_mapped_address(self):
         return self._has_prefix(0xffff, 96)
 
+    def is_isatap_address(self):
+        if not self.is_type_linklocal_unicast():
+            return False
+        return self.get_address() >> 32 & 0xffff == 0x5efe
+
+    def is_ipv4_translatable_address(self):
+        """Returns whether this IPv6 address uses the IPv4-embedded IPv6 well
+        known prefix. The prefix is currently defined in the draft RFC
+        [draft-ietf-behave-address-format-10]_.
+
+        .. [draft-ietf-behave-address-format-10]
+           http://tools.ietf.org/html/draft-ietf-behave-address-format-10
+
+        """
+        return self._has_prefix(0x64ff9b << 64, 96)
+
+    def embeds_ipv4_address(self):
+        """Returns whether this IPv6 address embeds an IPv4 address in the lower
+        32 bits. Currently recognized as IPv4 embedded addresses are
+        IPv4-mapped IPv6 addresses (see :meth:`is_ipv4_mapped_address`), ISATAP
+        addresses (:meth:`is_isatap_address`) and IPv4 translatable addresses
+        using the well-known prefix (:meth:`is_ipv4_translatable_address`).
+
+        >>> # IPv4-mapped IPv6 address
+        >>> IPv6Address('::ffff:192.0.2.128').embeds_ipv4_address()
+        True
+        >>> # ISATAP address
+        >>> IPv6Address('fe80::5efe:192.0.2.143').embeds_ipv4_address()
+        True
+        >>> # IPv4-Embedded IPv6 addresses using the Well-Known Prefix
+        >>> IPv6Address('64:ff9b::192.0.2.33').embeds_ipv4_address()
+        True
+
+        IPv4 embedded addresses will be represented with the IPv4 part in
+        dot-decimal notation by default when calling :func:`str` on the address.
+
+        """
+        return self.is_ipv4_mapped_address() \
+            or self.is_isatap_address() \
+            or self.is_ipv4_translatable_address()
+
     def as_ipv4(self):
-        if not self.is_ipv4_mapped_address():
+        if not self.embeds_ipv4_address():
             raise ValueError("%s is not an IPv4-mapped IPv6 value" % self)
         return IPv4Address(0xffffffff & self.get_address())
 
     def as_ipv6(self):
         return self
 
-    def __str__(self):
-        # FIXME implement canonical form correctly
-        return _AddressFormatter(
-            address_size = IPv6Address.BIT_SIZE,
+    def __str__(self, omit_leading_zeros=True, zero_compress=True,
+            lowercase=True, ipv4_aware=True):
+        address = self.get_address()
+        address_size = IPv6Address.BIT_SIZE
+        group_size = 16
+        separator = ':'
+
+        # Group format
+        group_format = "%"
+        if not omit_leading_zeros:
+            group_format += "04"
+        if lowercase:
+            group_format += "x"
+        else:
+            group_format += "X"
+
+        # Check if we need to encode the last 32 bit in IPv4 dotted decimal
+        has_ipv4 = False
+        if ipv4_aware and self.embeds_ipv4_address():
+            has_ipv4 = True
+            address >>= IPv4Address.BIT_SIZE
+            address_size = IPv6Address.BIT_SIZE - IPv4Address.BIT_SIZE
+
+        # Format the address
+        s = _AddressFormatter(
+            address_size = address_size,
             group_size = 16,
-            group_format = "%x",
-            separator = ':'
-        ).format(self)
+            group_format = group_format,
+            separator = separator,
+            compress = zero_compress
+        ).format(address)
+
+        # Append dot-decimal IPv4 address if embedded
+        if has_ipv4:
+            sep = separator
+            if s.endswith(separator):
+                sep = ''
+            s = "%s%s%s" % (s, sep, self.as_ipv4())
+
+        return s
 
     def __eq__(self, other):
         if not isinstance(other, IPv6Address):

File addrinfo/parser.py

 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 """
-    Address parsing
+    addrinfo.parser
     ---------------
 
     Provides functions for parsing string encoded addresses into numeric