2626import sys
2727import time
2828import types
29+ import re
2930
3031# Import ip_address/network and urlparse into the cpppo namespace. ip_address requires unicode, so
3132# we also provide a Python2 shim to ensure a str is interpreted as unicode, as well as provide
4142except ImportError :
4243 import repr as reprlib
4344
45+ try :
46+ xrange (0 ,1 )
47+ except NameError :
48+ xrange = range
49+
4450__author__ = "Perry Kundert"
45514652__copyright__ = "Copyright (c) 2013 Hard Consulting Corporation"
@@ -524,7 +530,8 @@ def wrapper( *args, **kwds ):
524530 return wrapper
525531 return decorator
526532
527- def hexdump ( src , length = 16 , sep = '.' ):
533+
534+ def hexdumper ( src , offset = 0 , length = 16 , sep = '.' , quote = '|' ):
528535 '''
529536 @brief Return {src} in hex dump.
530537 @param[in] length {Int} Nb Bytes by row.
@@ -535,12 +542,6 @@ def hexdump( src, length=16, sep='.' ):
535542 '''
536543 result = []
537544
538- # Python3 support
539- try :
540- xrange (0 ,1 );
541- except NameError :
542- xrange = range ;
543-
544545 for i in xrange (0 , len (src ), length ):
545546 subSrc = src [i :i + length ];
546547 hexa = '' ;
@@ -564,9 +565,105 @@ def hexdump( src, length=16, sep='.' ):
564565 text += chr (c );
565566 else :
566567 text += sep ;
567- result .append (('%08X: %-' + str (length * (2 + 1 )+ 1 )+ 's |%s|' ) % (i , hexa , text ));
568+ yield "{addr:08X}: {hexa:<{hexawidth}s} {quote}{text}{quote}" .format (
569+ addr = i + offset , hexa = hexa , hexawidth = length * (2 + 1 )+ 1 , text = text , quote = quote or '' )
570+
571+
572+ def hexdump ( src , offset = 0 , length = 16 , sep = '.' , quote = '|' ):
573+ return '\n ' .join ( hexdumper ( src , offset = offset , length = length , sep = sep , quote = quote ))
574+
575+
576+ def hexdump_differs ( * dumps , ** kwds ): # Python3 version: ', inclusive=False ):'
577+ """Compare a number of hexdump outputs side by side, returning differing lines."""
578+ inclusive = kwds .get ( 'inclusive' , False ) # for Python2 compatibility
579+ lines = [ d .split ( '\n ' ) for d in dumps ]
580+ differs = []
581+ for cols in zip ( * lines ):
582+ same = all ( c == cols [0 ] for c in cols [1 :] )
583+ if not same or inclusive :
584+ differs .append (( ' == ' if same else ' != ' ).join ( cols ))
585+ return '\n ' .join ( differs )
586+
587+
588+ def hexdecode ( enc , offset = 0 , sep = ':' ):
589+ """Decode hex octets "ab:cd:ef:01..." (starting at off bytes in) into b"\xab \xcd \xef \x01 ..." """
590+ return bytes (bytearray .fromhex ( '' .join ( enc .split ( sep ))))[offset :]
591+
592+
593+ def hexloader ( dump , offset = 0 , fill = False , skip = False ):
594+ """Load data from a iterable hex dump, eg, either as a sequence of rows or a string:
595+
596+ 00003FD0: 3F D0 00 00 00 00 00 00 00 00 00 00 12 00 00 00 |................|
597+
598+ 00003FF0: 3F F0 00 00 00 00 00 00 00 00 00 00 12 00 00 00 |................|
599+ 00004000: 40 00 30 31 20 53 45 34 20 45 20 32 33 2e 35 63 |@.01 SE4 E 23.5c|
600+
601+ Yields a corresponding sequence of address,bytes. To ignore the address
602+ and get the data:
603+
604+ b''.join( data for addr,data in hexload( ... )
605+
606+ If fill may be False/b'', or a single-byte value used to in-fill any missing
607+ address ranges.
608+
609+ If skip is Truthy, we allow and skip empty/non-matching lines.
610+ If gaps is Truthy, allow gaps in addresses.
611+ """
612+ if fill :
613+ assert isinstance ( fill , bytes ) and len ( fill ) == 1 , \
614+ "fill must be a bytes singleton, not {fill!r}" .format ( fill = fill )
615+ if isinstance ( dump , basestring if sys .version_info [0 ] < 3 else str ):
616+ dump = dump .split ( '\n ' )
617+ for row in dump :
618+ if not row .strip ():
619+ continue # all whitespace; ignore
620+ match = hexloader .parser .match ( row )
621+ if not match :
622+ assert skip , \
623+ "Failed to match a hex dump on row: {row!r}" .format ( row = row )
624+ continue
625+ addr = int ( match .group ( 'address' ), 16 )
626+ data = hexdecode ( match .group ( 'values' ), sep = ' ' )
627+
628+ if addr > offset :
629+ # row address is beyond current offset; fill, or skip offset ahead
630+ if fill :
631+ yield offset ,(fill * ( addr - offset ))
632+ offset = addr
633+ if addr < offset :
634+ # Row starts before desired offset; skip or clip
635+ if addr + len ( data ) <= offset :
636+ continue
637+ data = data [offset - addr :]
638+ addr = offset
639+ yield addr ,data
640+ offset = addr + len ( data )
641+
642+ hexloader .parser = re .compile (
643+ r"""^
644+ \s*
645+ (?P<address>
646+ {hexclass}{{1,16}} # address
647+ )
648+ [:]\s* # : whitespace
649+ (?P<values>
650+ (?:\s{{0,2}}{hexclass}{{2}})+ # hex pairs separated by 0-2 whitespace
651+ )
652+ (?:
653+ \s+ # whitespace at end
654+ (?P<quote>\|?) # | (optional ..print.. quote)
655+ (?P<print>
656+ .* # |..print..|
657+ )
658+ (?P=quote) # | (optional ..print.. quote)
659+ )? # entire ..print.. section optional
660+ $""" .format ( hexclass = '[0-9A-Fa-f]' ), re .VERBOSE )
661+
662+
663+ def hexload ( dump , offset = 0 , fill = False , skip = False ):
664+ """Return bytes data specified from dump"""
665+ return b'' .join ( d for a ,d in hexloader ( dump , offset = offset , fill = fill , skip = skip ))
568666
569- return '\n ' .join (result );
570667
571668#
572669# unicode, ip/network, parse_ip_port -- handle unicode/str IP addresses
0 commit comments