@@ -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
297411def record_unpack (data ,
298412 long_unpack = long_unpack ,
0 commit comments