Skip to content

Commit fcdc8f1

Browse files
committed
Implement RFC 1924 IPv6 encoding/decoding
Just for fun really. RFC 1924 encodings are not allowed by most existing methods. New long2rfc1924 and rfc19242long allow encoding/decoding to the 20 character ASCII encoding specified in RFC 1924. Closes #9
1 parent d698b00 commit fcdc8f1

1 file changed

Lines changed: 99 additions & 2 deletions

File tree

iptools/ipv6.py

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
'cidr2block',
3131
'ip2long',
3232
'long2ip',
33+
'long2rfc1924',
34+
'rfc19242long',
3335
'validate_cidr',
3436
'validate_ip',
3537
'DOCUMENTATION_NETWORK',
@@ -56,7 +58,7 @@
5658
)
5759

5860
#: Regex for validating an IPv6 in hex notation
59-
_HEX_RE = re.compile(r'^([0-9a-f]{0,4}:){2,7}[0-9a-f]{0,4}$')
61+
_HEX_RE = re.compile(r'^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$')
6062

6163
#: Regex for validating an IPv6 in dotted-quad notation
6264
_DOTTED_QUAD_RE = re.compile(r'^([0-9a-f]{0,4}:){2,6}(\d{1,3}\.){0,3}\d{1,3}$')
@@ -136,6 +138,21 @@
136138
#: All DHCP servers and relay agents on the local site
137139
MULTICAST_SITE_DHCP = "ff05::1:3"
138140

141+
#: RFC 1924 alphabet
142+
_RFC1924_ALPHABET = [
143+
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
144+
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
145+
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
146+
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
147+
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
148+
'!', '#', '$', '%', '&', '(', ')', '*', '+', '-', ';', '<', '=',
149+
'>', '?', '@', '^', '_', '`', '{', '|', '}', '~',
150+
]
151+
#: RFC 1924 reverse lookup
152+
_RFC1924_REV = None
153+
#: Regex for validating an IPv6 in hex notation
154+
_RFC1924_RE = re.compile(r'^[0-9A-Za-z!#$%&()*+-;<=>?@^_`{|}~]{20}$')
155+
139156

140157
def validate_ip(s):
141158
"""Validate a hexidecimal IPv6 ip address.
@@ -167,6 +184,8 @@ def validate_ip(s):
167184
Traceback (most recent call last):
168185
...
169186
TypeError: expected string or buffer
187+
>>> validate_ip('1080:0:0:0:8:800:200c:417a')
188+
True
170189
171190
172191
:param s: String to validate as a hexidecimal IPv6 ip address.
@@ -218,6 +237,9 @@ def ip2long(ip):
218237
True
219238
>>> ip2long('ff::ff::ff') == None
220239
True
240+
>>> expect = 21932261930451111902915077091070067066
241+
>>> ip2long('1080:0:0:0:8:800:200C:417A') == expect
242+
True
221243
222244
223245
:param ip: Hexidecimal IPv6 address
@@ -256,7 +278,7 @@ def ip2long(ip):
256278
# end ip2long
257279

258280

259-
def long2ip(l):
281+
def long2ip(l, rfc1924=False):
260282
"""Convert a network byte order 128-bit integer to a canonical IPv6
261283
address.
262284
@@ -281,17 +303,26 @@ def long2ip(l):
281303
Traceback (most recent call last):
282304
...
283305
TypeError: expected int between 0 and <really big int> inclusive
306+
>>> long2ip(ip2long('1080::8:800:200C:417A'), rfc1924=True)
307+
'4)+k&C#VzJ4br>0wv%Yp'
308+
>>> long2ip(ip2long('::'), rfc1924=True)
309+
'00000000000000000000'
284310
285311
286312
:param l: Network byte order 128-bit integer.
287313
:type l: int
314+
:param rfc1924: Encode in RFC 1924 notation (base 85)
315+
:type rfc1924: bool
288316
:returns: Canonical IPv6 address (eg. '::1').
289317
:raises: TypeError
290318
"""
291319
if MAX_IP < l or l < MIN_IP:
292320
raise TypeError(
293321
"expected int between %d and %d inclusive" % (MIN_IP, MAX_IP))
294322

323+
if rfc1924:
324+
return long2rfc1924(l)
325+
295326
# format as one big hex value
296327
hex_str = '%032x' % l
297328
# split into double octet chunks without padding zeros
@@ -323,6 +354,72 @@ def long2ip(l):
323354
# end long2ip
324355

325356

357+
def long2rfc1924(l):
358+
"""Convert a network byte order 128-bit integer to an rfc1924 IPv6
359+
address.
360+
361+
362+
>>> long2rfc1924(ip2long('1080::8:800:200C:417A'))
363+
'4)+k&C#VzJ4br>0wv%Yp'
364+
>>> long2rfc1924(ip2long('::'))
365+
'00000000000000000000'
366+
>>> long2rfc1924(MAX_IP)
367+
'=r54lj&NUUO~Hi%c2ym0'
368+
369+
370+
:param l: Network byte order 128-bit integer.
371+
:type l: int
372+
:returns: RFC 1924 IPv6 address
373+
:raises: TypeError
374+
"""
375+
if MAX_IP < l or l < MIN_IP:
376+
raise TypeError(
377+
"expected int between %d and %d inclusive" % (MIN_IP, MAX_IP))
378+
o = []
379+
r = l
380+
while r > 85:
381+
o.append(_RFC1924_ALPHABET[r % 85])
382+
r = r // 85
383+
o.append(_RFC1924_ALPHABET[r])
384+
return ''.join(reversed(o)).zfill(20)
385+
386+
387+
def rfc19242long(s):
388+
"""Convert an RFC 1924 IPv6 address to a network byte order 128-bit
389+
integer.
390+
391+
392+
>>> expect = 0
393+
>>> rfc19242long('00000000000000000000') == expect
394+
True
395+
>>> expect = 21932261930451111902915077091070067066
396+
>>> rfc19242long('4)+k&C#VzJ4br>0wv%Yp') == expect
397+
True
398+
>>> rfc19242long('pizza') == None
399+
True
400+
>>> rfc19242long('~~~~~~~~~~~~~~~~~~~~') == None
401+
True
402+
>>> rfc19242long('=r54lj&NUUO~Hi%c2ym0') == MAX_IP
403+
True
404+
405+
406+
:param ip: RFC 1924 IPv6 address
407+
:type ip: str
408+
:returns: Network byte order 128-bit integer or ``None`` if ip is invalid.
409+
"""
410+
global _RFC1924_REV
411+
if not _RFC1924_RE.match(s):
412+
return None
413+
if _RFC1924_REV is None:
414+
_RFC1924_REV = {v: k for k, v in enumerate(_RFC1924_ALPHABET)}
415+
l = 0
416+
for c in s:
417+
l = l * 85 + _RFC1924_REV[c]
418+
if l > MAX_IP:
419+
return None
420+
return l
421+
422+
326423
def validate_cidr(s):
327424
"""Validate a CIDR notation ip address.
328425

0 commit comments

Comments
 (0)