s4-python: Format to PEP8, simplify tests.
[samba.git] / source4 / scripting / python / samba_external / dnspython / dns / ipv6.py
1 # Copyright (C) 2003-2007, 2009, 2010 Nominum, Inc.
2 #
3 # Permission to use, copy, modify, and distribute this software and its
4 # documentation for any purpose with or without fee is hereby granted,
5 # provided that the above copyright notice and this permission notice
6 # appear in all copies.
7 #
8 # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
9 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
11 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
14 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
16 """IPv6 helper functions."""
17
18 import re
19
20 import dns.exception
21 import dns.ipv4
22
23 _leading_zero = re.compile(r'0+([0-9a-f]+)')
24
25 def inet_ntoa(address):
26     """Convert a network format IPv6 address into text.
27
28     @param address: the binary address
29     @type address: string
30     @rtype: string
31     @raises ValueError: the address isn't 16 bytes long
32     """
33
34     if len(address) != 16:
35         raise ValueError("IPv6 addresses are 16 bytes long")
36     hex = address.encode('hex_codec')
37     chunks = []
38     i = 0
39     l = len(hex)
40     while i < l:
41         chunk = hex[i : i + 4]
42         # strip leading zeros.  we do this with an re instead of
43         # with lstrip() because lstrip() didn't support chars until
44         # python 2.2.2
45         m = _leading_zero.match(chunk)
46         if not m is None:
47             chunk = m.group(1)
48         chunks.append(chunk)
49         i += 4
50     #
51     # Compress the longest subsequence of 0-value chunks to ::
52     #
53     best_start = 0
54     best_len = 0
55     start = -1
56     last_was_zero = False
57     for i in xrange(8):
58         if chunks[i] != '0':
59             if last_was_zero:
60                 end = i
61                 current_len = end - start
62                 if current_len > best_len:
63                     best_start = start
64                     best_len = current_len
65                 last_was_zero = False
66         elif not last_was_zero:
67             start = i
68             last_was_zero = True
69     if last_was_zero:
70         end = 8
71         current_len = end - start
72         if current_len > best_len:
73             best_start = start
74             best_len = current_len
75     if best_len > 0:
76         if best_start == 0 and \
77            (best_len == 6 or
78             best_len == 5 and chunks[5] == 'ffff'):
79             # We have an embedded IPv4 address
80             if best_len == 6:
81                 prefix = '::'
82             else:
83                 prefix = '::ffff:'
84             hex = prefix + dns.ipv4.inet_ntoa(address[12:])
85         else:
86             hex = ':'.join(chunks[:best_start]) + '::' + \
87                   ':'.join(chunks[best_start + best_len:])
88     else:
89         hex = ':'.join(chunks)
90     return hex
91
92 _v4_ending = re.compile(r'(.*):(\d+)\.(\d+)\.(\d+)\.(\d+)$')
93 _colon_colon_start = re.compile(r'::.*')
94 _colon_colon_end = re.compile(r'.*::$')
95
96 def inet_aton(text):
97     """Convert a text format IPv6 address into network format.
98
99     @param text: the textual address
100     @type text: string
101     @rtype: string
102     @raises dns.exception.SyntaxError: the text was not properly formatted
103     """
104
105     #
106     # Our aim here is not something fast; we just want something that works.
107     #
108
109     if text == '::':
110         text = '0::'
111     #
112     # Get rid of the icky dot-quad syntax if we have it.
113     #
114     m = _v4_ending.match(text)
115     if not m is None:
116         text = "%s:%04x:%04x" % (m.group(1),
117                                  int(m.group(2)) * 256 + int(m.group(3)),
118                                  int(m.group(4)) * 256 + int(m.group(5)))
119     #
120     # Try to turn '::<whatever>' into ':<whatever>'; if no match try to
121     # turn '<whatever>::' into '<whatever>:'
122     #
123     m = _colon_colon_start.match(text)
124     if not m is None:
125         text = text[1:]
126     else:
127         m = _colon_colon_end.match(text)
128         if not m is None:
129             text = text[:-1]
130     #
131     # Now canonicalize into 8 chunks of 4 hex digits each
132     #
133     chunks = text.split(':')
134     l = len(chunks)
135     if l > 8:
136         raise dns.exception.SyntaxError
137     seen_empty = False
138     canonical = []
139     for c in chunks:
140         if c == '':
141             if seen_empty:
142                 raise dns.exception.SyntaxError
143             seen_empty = True
144             for i in xrange(0, 8 - l + 1):
145                 canonical.append('0000')
146         else:
147             lc = len(c)
148             if lc > 4:
149                 raise dns.exception.SyntaxError
150             if lc != 4:
151                 c = ('0' * (4 - lc)) + c
152             canonical.append(c)
153     if l < 8 and not seen_empty:
154         raise dns.exception.SyntaxError
155     text = ''.join(canonical)
156
157     #
158     # Finally we can go to binary.
159     #
160     try:
161         return text.decode('hex_codec')
162     except TypeError:
163         raise dns.exception.SyntaxError