2012-08-07 6 views
6

Estoy tratando de usar struct.unpack() para desarmar un registro de datos que finaliza con una cadena ASCII.Desempaquetar una estructura que termina con una cadena ASCIIZ

El registro (que pasa a ser un TomTom ov2 registro) tiene este formato (almacenado ascendente hacia la izquierda):

  • 1 byte
  • 4 bytes int para el tamaño total del registro (incluyendo este campo)
  • 4 byte int
  • 4 byte int
  • cadena de longitud variable, terminada en nulo

unpack() requiere que la longitud de la cadena se incluya en el formato que la pasa. Puedo usar el segundo campo y el tamaño conocido del resto del registro - 13 bytes - para obtener la longitud de la cadena:

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

luego proceder con el desembalaje completo, pero ya que la cadena está terminada en cero , Realmente desearía que unpack() lo hiciera por mí. También sería bueno tener esto si me topa con una estructura que no incluye su propio tamaño.

¿Cómo puedo lograrlo?

+0

Aunque he respondido a esta a mí mismo para compartir la solución que se me ocurrió, me gustaría ver a los demás. –

Respuesta

5

El registro sin tamaño es bastante fácil de manejar, en realidad, ya que struct.calcsize() le dirá la longitud que espera. Puede usar eso y la longitud real de los datos para construir una nueva cadena de formato para unpack() que incluya la longitud de cadena correcta.

Esta función es sólo un envoltorio para unpack(), lo que permite un nuevo carácter de formato en la última posición que va a caer el terminal de 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

he hecho dos nuevas funciones que deben ser utilizables como reemplazos directos para las funciones estándar de paquete y desembalaje. Ambos admiten el carácter 'z' para empaquetar/descomprimir una cadena ASCIIZ. No existen restricciones a la ubicación o el número de veces que aparece el carácter 'z' en la cadena de formato:

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) 

Aquí hay un ejemplo de cómo usarlos:

>>> 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) 
>>> 
Cuestiones relacionadas