2012-08-07 14 views
6

Próbuję użyć struct.unpack(), aby rozebrać rekord danych zakończony łańcuchem ASCII.Rozpakowywanie zakończenia struct za pomocą ciągu ASCIIZ

Rekord (zdarza się TomTom ov2 zapis) ma następujący format (przechowywany mało endiany):

  • 1 bajt
  • 4 bajtów INT całkowitego rozmiaru danych (w tym tej dziedzinie)
  • 4 bajt int
  • 4 bajt int
  • zmiennej długości ciąg zakończony zerem

unpack() wymaga, aby długość napisu była uwzględniona w przekazanym przez niego formacie. Mogę użyć pola drugiego i znaną wielkość reszty rekordu - 13 bajtów - aby uzyskać długość naciągu:

str_len = struct.unpack("<xi", record[:5])[0] - 13 
fmt = "<biii{0}s".format(str_len) 

następnie przystąpić do pełnej rozpakowanie, ale ponieważ łańcuch jest zakończony zerem , Naprawdę chciałbym, aby zrobiło to dla mnie unpack(). Byłoby również miło mieć to, czy powinienem uruchomić system, który nie zawiera własnego rozmiaru.

Jak mogę to zrobić?

+0

Chociaż ja odpowiedział ten siebie, aby podzielić się rozwiązaniem, które wymyśliłem, chętnie bym zobaczył innych. –

Odpowiedz

5

Rekord bez rozmiaru jest dość łatwy w obsłudze, w rzeczywistości, ponieważ struct.calcsize() powie Ci długość, której oczekuje. Możesz użyć tej i rzeczywistej długości danych do skonstruowania nowego ciągu formatu dla unpack(), który zawiera poprawną długość ciągu.

Ta funkcja jest tylko nakładką na unpack(), dzięki czemu nowy format znaku w ostatniej pozycji, która spadnie terminala NUL:

import struct 
def unpack_with_final_asciiz(fmt, dat): 
    """ 
    Unpack binary data, handling a null-terminated string at the end 
    (and only at the end) automatically. 

    The first argument, fmt, is a struct.unpack() format string with the 
    following modfications: 
    If fmt's last character is 'z', the returned string will drop the NUL. 
    If it is 's' with no length, the string including NUL will be returned. 
    If it is 's' with a length, behavior is identical to normal unpack(). 
    """ 
    # Just pass on if no special behavior is required 
    if fmt[-1] not in ('z', 's') or (fmt[-1] == 's' and fmt[-2].isdigit()): 
     return struct.unpack(fmt, dat) 

    # Use format string to get size of contained string and rest of record 
    non_str_len = struct.calcsize(fmt[:-1]) 
    str_len = len(dat) - non_str_len 

    # Set up new format string 
    # If passed 'z', treat terminating NUL as a "pad byte" 
    if fmt[-1] == 'z': 
     str_fmt = "{0}sx".format(str_len - 1) 
    else: 
     str_fmt = "{0}s".format(str_len) 
    new_fmt = fmt[:-1] + str_fmt 

    return struct.unpack(new_fmt, dat) 

>>> dat = b'\x02\x1e\x00\x00\x00z\x8eJ\x00\xb1\x7f\x03\x00Down by the river\x00' 
>>> unpack_with_final_asciiz("<biiiz", dat) 
(2, 30, 4886138, 229297, b'Down by the river') 
6

zrobiłem dwie nowe funkcje, które powinny być użyteczne jako zamienniki do standardowych funkcji pakowania i rozpakowywania. Oba obsługują znak "z", aby spakować/rozpakować ciąg znaków ASCIIZ. Nie ma żadnych ograniczeń co do lokalizacji lub liczbie wystąpień znaku „Z” w łańcuchu format:

import struct 

def unpack (format, buffer) : 
    while True : 
     pos = format.find ('z') 
     if pos < 0 : 
      break 
     asciiz_start = struct.calcsize (format[:pos]) 
     asciiz_len = buffer[asciiz_start:].find('\0') 
     format = '%s%dsx%s' % (format[:pos], asciiz_len, format[pos+1:]) 
    return struct.unpack (format, buffer) 

def pack (format, *args) : 
    new_format = '' 
    arg_number = 0 
    for c in format : 
     if c == 'z' : 
      new_format += '%ds' % (len(args[arg_number])+1) 
      arg_number += 1 
     else : 
      new_format += c 
      if c in 'cbB?hHiIlLqQfdspP' : 
       arg_number += 1 
    return struct.pack (new_format, *args) 

Oto przykład, w jaki sposób z nich korzystać:

>>> from struct_z import pack, unpack 
>>> line = pack ('<izizi', 1, 'Hello', 2, ' world!', 3) 
>>> print line.encode('hex') 
0100000048656c6c6f000200000020776f726c64210003000000 
>>> print unpack ('<izizi',line) 
(1, 'Hello', 2, ' world!', 3) 
>>> 
Powiązane problemy