Skip to content

Commit a80e2a8

Browse files
author
James William Pye
committed
Commit Matthew Grant's inet/cidr/macaddr support patch.
1 parent f680ac7 commit a80e2a8

4 files changed

Lines changed: 168 additions & 51 deletions

File tree

postgresql/test/test_driver.py

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -278,39 +278,33 @@
278278
Varbit('010111101111'),
279279
],
280280
),
281-
]
282-
283-
if False:
284-
# When an implementation does make it,
285-
# re-enable these tests.
286-
type_samples.append((
287-
'inet', [
288-
IPAddress4('255.255.255.255'),
289-
IPAddress4('127.0.0.1'),
290-
IPAddress4('10.0.0.1'),
291-
IPAddress4('0.0.0.0'),
292-
IPAddress6('::1'),
293-
IPAddress6('ffff' + ':ffff'*7),
294-
IPAddress6('fe80::1'),
295-
IPAddress6('fe80::1'),
296-
IPAddress6('0::0'),
281+
('inet', [
282+
'255.255.255.255',
283+
'127.0.0.1',
284+
'10.0.0.1',
285+
'0.0.0.0',
286+
'::1',
287+
'ffff' + ':ffff'*7,
288+
'fe80::1',
289+
'fe80::1',
290+
'::', # 0::0
297291
],
298-
))
299-
type_samples.append((
300-
'cidr', [
301-
IPNetwork4('255.255.255.255/32'),
302-
IPNetwork4('127.0.0.0/8'),
303-
IPNetwork4('127.1.0.0/16'),
304-
IPNetwork4('10.0.0.0/32'),
305-
IPNetwork4('0.0.0.0/0'),
306-
IPNetwork6('ffff' + ':ffff'*7 + '/128'),
307-
IPNetwork6('::1/128'),
308-
IPNetwork6('fe80::1/128'),
309-
IPNetwork6('fe80::0/64'),
310-
IPNetwork6('fe80::0/16'),
311-
IPNetwork6('0::0/0'),
292+
),
293+
('cidr', [
294+
'255.255.255.255/32',
295+
'127.0.0.0/8',
296+
'127.1.0.0/16',
297+
'10.0.0.0/32',
298+
'0.0.0.0/0',
299+
'ffff' + ':ffff'*7 + '/128',
300+
'::1/128',
301+
'fe80::1/128',
302+
'fe80::/64',
303+
'fe80::/16',
304+
'::/0',
312305
],
313-
))
306+
),
307+
]
314308

315309
class test_driver(unittest.TestCase):
316310
@pg_tmp

postgresql/types/io/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,13 @@
3737
pg_types.VARBITOID,
3838
),
3939

40-
'pg_system': (
40+
'pg_network': (
41+
pg_types.MACADDROID,
42+
pg_types.INETOID,
43+
pg_types.CIDROID,
44+
),
45+
46+
'pg_system': (
4147
pg_types.OIDOID,
4248
pg_types.XIDOID,
4349
pg_types.CIDOID,

postgresql/types/io/lib.py

Lines changed: 127 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -269,30 +269,144 @@ def varbit_unpack(data, long_unpack = long_unpack):
269269
"""
270270
return long_unpack(data[0:4]), data[4:]
271271

272-
def net_pack(family_mask_data, len = len):
272+
from socket import \
273+
AF_INET, AF_INET6, \
274+
inet_pton, inet_ntop
275+
from socket import error as socket_error
276+
# From PGSQL src/include/utils/inet.h
277+
_PGSQL_AF_INET = AF_INET
278+
_PGSQL_AF_INET6 = AF_INET + 1
279+
280+
def net_pack(inet, len = len):
273281
"""
274-
Given a triple, yield the serialized form for transport.
282+
Given a string inet/cidr, yield the serialized form for transport.
275283
276284
Prepends the ``family``, ``mask`` and implicit ``is_cidr`` fields.
277285
278286
Supports cidr and inet types.
279287
"""
280-
(family, mask, data) = family_mask_data
281-
return bytes((family, mask, 1, len(data))) + data
282-
283-
def net_unpack(data):
284-
"""
285-
Given serialized cidr data, return a tuple:
286-
287-
(family, mask, data)
288+
slash_index = inet.find('/')
289+
if slash_index >= 0:
290+
try:
291+
mask = int(inet[slash_index+1:])
292+
except ValueError:
293+
raise ValueError('invalid mask in inet/cidr')
294+
address = inet[:slash_index]
295+
else:
296+
mask = 0
297+
address = inet
298+
if inet.find(':') >= 0:
299+
family = _PGSQL_AF_INET6
300+
posix_family = AF_INET6
301+
max_mask = 128
302+
else:
303+
family = _PGSQL_AF_INET
304+
posix_family = AF_INET
305+
max_mask = 32
306+
#If IPv4 address is short, right pad it so that it is valid
307+
num_bytes = len(address.split('.'))
308+
if num_bytes < 4:
309+
address += (4 - num_bytes) * '.0'
310+
try:
311+
data = inet_pton(posix_family, address)
312+
except socket_error as exc:
313+
raise ValueError(str(exc)) from exc
314+
if mask:
315+
if not (0 <= mask <= max_mask):
316+
raise ValueError('invalid mask in inet/cidr')
317+
# Calculate optional cidr byte - PGSQL ignores this on input
318+
i_address = int.from_bytes(data, byteorder='big', signed=False)
319+
i_mask = ~(2**(max_mask-mask)-1)
320+
i_net = i_address & i_mask
321+
is_cidr = 1 if (i_net == i_address) else 0
322+
else:
323+
is_cidr = 0
324+
return bytes((family, mask, is_cidr, len(data))) + data
325+
326+
def net_unpack(data, cidr=False, len=len):
327+
"""
328+
Given serialized cidr data, return :
329+
330+
Python string cidr/network representation
288331
"""
289332
family, mask, is_cidr, size = data[:4]
290333

334+
if family == _PGSQL_AF_INET:
335+
posix_family = AF_INET
336+
max_mask = 32
337+
proper_size = 4
338+
elif family == _PGSQL_AF_INET6:
339+
posix_family = AF_INET6
340+
max_mask = 128
341+
proper_size = 16
342+
else:
343+
raise ValueError("invalid family parameter")
291344
rd = data[4:]
292-
if len(rd) != size:
345+
rd_len = len(rd)
346+
if rd_len != size and rd_len != proper_size:
293347
raise ValueError("invalid size parameter")
294-
295-
return (family, mask, rd)
348+
try:
349+
address = inet_ntop(posix_family, rd)
350+
except socket_error as exc:
351+
raise ValueError(str(exc)) from exc
352+
if not (0 <= mask <= max_mask):
353+
raise ValueError("invalid mask parameter")
354+
if cidr or mask:
355+
result = address + '/' + str(mask)
356+
else:
357+
result = address
358+
return result
359+
360+
def cidr_unpack(data):
361+
"""
362+
Variant of above for CIDR
363+
"""
364+
return net_unpack(data, cidr=True)
365+
366+
def macaddr_pack(data):
367+
"""
368+
Pack a MAC address
369+
370+
Format found in PGSQL src/backend/utils/adt/mac.c, and PGSQL Manual types
371+
"""
372+
# Accept all possible PGSQL Macaddr formats as in manual
373+
# Oh for sscanf() as we could just copy PGSQL C in src/util/adt/mac.c
374+
colon_parts = data.split(':')
375+
dash_parts = data.split('-')
376+
dot_parts = data.split('.')
377+
if len(colon_parts) == 6:
378+
mac_parts = colon_parts
379+
elif len(dash_parts) == 6:
380+
mac_parts = dash_parts
381+
elif len(colon_parts) == 2:
382+
mac_parts = [colon_parts[0][:2], colon_parts[0][2:4], colon_parts[0][4:],
383+
colon_parts[1][:2], colon_parts[1][2:4], colon_parts[1][4:]]
384+
elif len(dash_parts) == 2:
385+
mac_parts = [dash_parts[0][:2], dash_parts[0][2:4], dash_parts[0][4:],
386+
dash_parts[1][:2], dash_parts[1][2:4], dash_parts[1][4:]]
387+
elif len(dot_parts) == 3:
388+
mac_parts = [dot_parts[0][:2], dot_parts[0][2:], dot_parts[1][:2],
389+
dot_parts[1][2:], dot_parts[2][:2], dot_parts[2][2:]]
390+
elif len(colon_parts) == 1:
391+
mac_parts = [data[:2], data[2:4], data[4:6], data[6:8], data[8:10], data[10:]]
392+
else:
393+
raise ValueError('data string cannot be parsed to bytes')
394+
if len(mac_parts) != 6 and len(mac_parts[-1]) != 2:
395+
raise ValueError('data string cannot be parsed to bytes')
396+
macaddr = bytearray([int(p, 16) for p in mac_parts])
397+
return bytes(macaddr)
398+
399+
def macaddr_unpack(data):
400+
"""
401+
Unpack a MAC address
402+
403+
Format found in PGSQL src/backend/utils/adt/mac.c
404+
"""
405+
# This is easy, just go for standard macaddr format,
406+
# just like PGSQL in src/util/adt/mac.c macaddr_out()
407+
if len(data) != 6:
408+
raise ValueError('macaddr has incorrect length')
409+
return ("%02x:%02x:%02x:%02x:%02x:%02x" % tuple(data))
296410

297411
def record_unpack(data,
298412
long_unpack = long_unpack,

postgresql/types/io/pg_network.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
from .. import INETOID, CIDROID, MACADDROID
22
from . import lib
33

4-
#INETOID : (lib.net_pack, lib.net_unpack),
5-
#CIDROID : (lib.net_pack, lib.net_unpack),
6-
74
oid_to_io = {
8-
MACADDROID : (None, None),
9-
CIDROID : (None, None),
10-
INETOID : (None, None),
5+
MACADDROID : (lib.macaddr_pack, lib.macaddr_unpack, str),
6+
CIDROID : (lib.net_pack, lib.cidr_unpack, str),
7+
INETOID : (lib.net_pack, lib.net_unpack, str),
8+
}
9+
10+
oid_to_type = {
11+
MACADDROID : str,
12+
CIDROID : str,
13+
INETOID : str,
1114
}

0 commit comments

Comments
 (0)