2008-11-26 23 views
18

¿Hay alguna biblioteca o guía para leer y analizar datos binarios en C?Analizando datos binarios en C?

Estoy buscando alguna funcionalidad que reciba paquetes TCP en un socket de red y luego analizar esos datos binarios de acuerdo con una especificación, convirtiendo la información en una forma más utilizable por el código.

¿Hay bibliotecas por ahí que hacen esto, o incluso una introducción a la realización de este tipo de cosas?

Respuesta

15

La forma estándar de hacer esto en C/C++ es muy casting para estructuras como 'gwaredd' sugirió

No es tan peligroso como uno podría pensar. Primero debe enviar a la estructura que esperaba, como en su ejemplo, y luego, prueba esa estructura para determinar su validez. Debe probar valores máximos/mínimos, secuencias de terminación, etc.

En cualquier plataforma en la que se encuentre, debe leer Unix Network Programming, Volume 1: The Sockets Networking API. Cómprelo, pídalo prestado, robarlo (la víctima lo entenderá, es como robar comida o algo así ...), pero léalo.

Después de leer el Stevens, la mayor parte de esto tendrá mucho más sentido.

+1

Soy escéptico del método "cast then check". Si no lo haces, te arriesgas a obtener datos no válidos. Y si lo compruebas, ¿qué sentido tiene lanzar? La comprobación será tan lenta como el análisis tradicional. – bortzmeyer

+1

Como escribió Casey Barker a continuación, las cosas no son tan simples. Puede corregir alineación y relleno de bytes la mayor parte del tiempo (y debe tenerlo en cuenta y probarlo exhaustivamente con cada sistema nuevo), pero una vez que se encuentre con problemas de orden endian, se verá forzado a corregir cada estructura individualmente antes de verificar para la validez Y si está comprobando la validez, entonces también puede verificarlo durante el análisis. El análisis de tokens individuales también permite crear subclases y versiones de grano fino. – Groo

+0

De hecho, la validación de archivos de Office introducida en Office 2010 y posterior transferida a Office 2007 y Office 2003 básicamente verifica la validez del archivo para evitar la explotación de vulnerabilidades. –

3

Realmente no necesita analizar datos binarios en C, simplemente coloque un puntero a lo que crea que debería ser.

struct SomeDataFormat 
{ 
    .... 
} 

SomeDataFormat* pParsedData = (SomeDataFormat*) pBuffer; 

Sólo ten cuidado con las cuestiones endian, tamaños, la lectura fuera de la final de tampones, etc, etc

+4

O compiladores diferentes, etc. Ese es * realmente * código frágil, IMO. –

+0

De acuerdo. Creo que todos los muchos etc. son por qué él quiere que una biblioteca lo haga. –

+0

sí -.- aunque ese enfoque es razonable siempre y cuando esté en la misma máquina, realmente debería evitarse hacerlo en la programación de la red. –

5

que podría estar interesado en Google Protocol Buffers, que es básicamente un marco de serialización. Es principalmente para C++/Java/Python (esos son los idiomas admitidos por Google), pero hay esfuerzos constantes para transferirlo a otros idiomas, incluido el C. (No he utilizado el puerto C en absoluto, pero soy responsable de uno de los puertos C#.)

+1

Hay muchas formas de serializar datos (Protocol Buffers es bueno, pero es solo uno de ellos, también hay XML, JSON, ASN/1 + BER, etc.). Funcionan solo si controlas la especificación del protocolo. Si no es el caso, su método no funciona. – bortzmeyer

+0

Absolutamente. Si no tienes el control del protocolo, básicamente tienes que hacerlo manualmente. –

1

No entiendo realmente qué tipo de biblioteca estás buscando? ¿Biblioteca genérica que tomará cualquier entrada binaria y la analizará en un formato desconocido? No estoy seguro de que exista dicha biblioteca en ningún idioma. Creo que necesita elaborar su pregunta un poco.

Editar:
Ok, así que después de leer Jon's respuesta parece hay una biblioteca, así especie de biblioteca es más como herramienta de generación de código. Pero como muchos declararon simplemente transfiriendo los datos a la estructura de datos apropiada, con la debida precaución, es decir, utilizando estructuras empaquetadas y ocupándose de los problemas de Endian, usted es bueno. Usar dicha herramienta con C es solo una exageración.

2

El análisis/formateo de estructuras binarias es una de las muy pocas cosas que es más fácil de hacer en C que en lenguajes de alto nivel/administrados. Simplemente defina una estructura que corresponda al formato que desea manejar y la estructura es el analizador/formateador. Esto funciona porque una estructura en C representa un diseño de memoria preciso (que, por supuesto, ya es binario). Ver también las respuestas de kervin y gwaredd.

1

Básicamente, las sugerencias sobre la fundición de struct funcionan, pero tenga en cuenta que los números pueden representarse de forma diferente en diferentes arquitecturas.

Para tratar con problemas de endian, se introdujo el orden de bytes de la red. La práctica común es convertir números de orden de bytes de host a orden de bytes de red antes de enviar los datos y volver a la orden de host al recibirlos. Ver funciones htonl, htons, ntohl y ntohs.

Y realmente considere los consejos de kervin: lea UNP. ¡No te arrepentirás!

12

Permítame replantear su pregunta para ver si entendí correctamente. Usted es buscando un software que tomará una descripción formal de un paquete y luego producirá un "decodificador" para analizar dichos paquetes?

Si es así, la referencia en ese campo es PADS. Un buen artículo al introducirlo es PADS: A Domain-Specific Language for Processing Ad Hoc Data. PADS es muy completo pero desafortunadamente bajo una licencia no libre.

Hay posibles alternativas (no mencioné las soluciones que no son C ). Al parecer, ninguno puede ser considerado como completamente listo para la producción:

Si lee francés, que resumen estas cuestiones en Génération de décodeurs de formats binaires.

+0

@bortzmeyer Estas son todas noticias para mí. Gracias por la info! – Bklyn

10

En mi experiencia, la mejor manera es escribir primero un conjunto de primitivas, leer/escribir un valor único de algún tipo desde un búfer binario. Esto le proporciona una gran visibilidad y una forma muy sencilla de manejar cualquier problema de endianness: simplemente haga que las funciones lo hagan bien.

Luego, puede definir struct s para cada uno de sus mensajes de protocolo, y las funciones de escribir paquete/descomprimir (algunas personas los llaman serializar/deserializar) para cada uno.

Como caso base, una primitiva para extraer un único entero de 8 bits podría tener el siguiente aspecto (suponiendo un char de 8 bits en el equipo host, puede agregar una capa de tipos personalizados para asegurarse también, si es necesario):

const void * read_uint8(const void *buffer, unsigned char *value) 
{ 
    const unsigned char *vptr = buffer; 
    *value = *buffer++; 
    return buffer; 
} 

Aquí, elegí devolver el valor por referencia y devolver un puntero actualizado. Esta es una cuestión de gusto, por supuesto, puede devolver el valor y actualizar el puntero por referencia. Es una parte crucial del diseño que la función de lectura actualice el puntero para hacer que estos se puedan encadenar.

Ahora, podemos escribir una función similar para leer una cantidad sin signo de 16 bits:

const void * read_uint16(const void *buffer, unsigned short *value) 
{ 
    unsigned char lo, hi; 

    buffer = read_uint8(buffer, &hi); 
    buffer = read_uint8(buffer, &lo); 
    *value = (hi << 8) | lo; 
    return buffer; 
} 

Aquí asumí datos entrante es big-endian, esto es común en protocolos de red (principalmente por razones históricas) . Por supuesto, puede ser inteligente y hacer algunos cálculos aritméticos y eliminar la necesidad de un temporal, pero me parece que esto hace que sea más claro y más fácil de entender. Tener una transparencia máxima en este tipo de primitiva puede ser una buena cosa cuando se depura.

El siguiente paso sería comenzar a definir los mensajes específicos del protocolo y escribir las primitivas de lectura/escritura para que coincidan.En ese nivel, piense en la generación de código; si su protocolo se describe en un formato general legible por máquina, puede generar funciones de lectura/escritura a partir de eso, lo que ahorra mucho dolor. Esto es más difícil si el formato del protocolo es clever enough, pero a menudo es factible y muy recomendable.

+0

Quiere decir * vptr ++, ¿no? También falta el reparto de búfer a char *. – mloskot

28

Tengo que estar en desacuerdo con muchas de las respuestas aquí. Le sugiero que evite la tentación de enviar una estructura a los datos entrantes. Parece convincente e incluso podría funcionar en su objetivo actual, pero si el código se transporta alguna vez a otro objetivo/entorno/compilador, se encontrará con problemas. Algunas razones:

Endianness: La arquitectura que está utilizando en este momento podría ser big-endian, pero su próximo objetivo podría ser little-endian. O viceversa. Puede superar esto con macros (ntoh y hton, por ejemplo), pero es un trabajo extra y debe asegurarse de llamar a esas macros cada vez que haga referencia al campo.

Alineación: La arquitectura que está utilizando podría ser capaz de cargar una palabra de varios bytes en un desplazamiento impactado, pero muchas arquitecturas no pueden. Si una palabra de 4 bytes abarca un límite de alineación de 4 bytes, la carga puede tirar basura. Incluso si el protocolo en sí no tiene palabras desalineadas, a veces el flujo de bytes está desalineado. (Por ejemplo, aunque la definición de la cabecera IP pone todas las palabras de 4 bytes en límites de 4 bytes, a menudo la cabecera Ethernet empuja la cabecera IP sí mismo en un límite de 2 bytes.)

Relleno: Su compilador puede elegir para empaquetar su estructura firmemente sin relleno, o puede insertar relleno para hacer frente a las restricciones de alineación del objetivo. He visto este cambio entre dos versiones del mismo compilador. Puede usar #pragmas para forzar el problema, pero #pragmas son, por supuesto, específicos del compilador.

Orden de bits: El orden de los bits dentro de los campos de bits C es específico del compilador. Además, los bits son difíciles de "obtener" para su código de tiempo de ejecución. Cada vez que hace referencia a un campo de bits dentro de una estructura, el compilador tiene que usar un conjunto de operaciones de máscara/desplazamiento. Por supuesto, vas a tener que hacer ese enmascaramiento/cambio en algún momento, pero es mejor no hacerlo en cada referencia si la velocidad es una preocupación. (Si el espacio es la preocupación primordial, utilice campos de bits, pero pise con cuidado.)

Todo esto no quiere decir "no use estructuras". Mi enfoque favorito es declarar una estructura amigable endian nativa de todos los datos de protocolo relevantes sin ningún campo de bits y sin preocuparse por los problemas, luego escribir un conjunto de rutinas paquete simétrico/parse que utilizan la estructura como un intermediario.

typedef struct _MyProtocolData 
{ 
    Bool myBitA; // Using a "Bool" type wastes a lot of space, but it's fast. 
    Bool myBitB; 
    Word32 myWord; // You have a list of base types like Word32, right? 
} MyProtocolData; 

Void myProtocolParse(const Byte *pProtocol, MyProtocolData *pData) 
{ 
    // Somewhere, your code has to pick out the bits. Best to just do it one place. 
    pData->myBitA = *(pProtocol + MY_BITS_OFFSET) & MY_BIT_A_MASK >> MY_BIT_A_SHIFT; 
    pData->myBitB = *(pProtocol + MY_BITS_OFFSET) & MY_BIT_B_MASK >> MY_BIT_B_SHIFT; 

    // Endianness and Alignment issues go away when you fetch byte-at-a-time. 
    // Here, I'm assuming the protocol is big-endian. 
    // You could also write a library of "word fetchers" for different sizes and endiannesses. 
    pData->myWord = *(pProtocol + MY_WORD_OFFSET + 0) << 24; 
    pData->myWord += *(pProtocol + MY_WORD_OFFSET + 1) << 16; 
    pData->myWord += *(pProtocol + MY_WORD_OFFSET + 2) << 8; 
    pData->myWord += *(pProtocol + MY_WORD_OFFSET + 3); 

    // You could return something useful, like the end of the protocol or an error code. 
} 

Void myProtocolPack(const MyProtocolData *pData, Byte *pProtocol) 
{ 
    // Exercise for the reader! :) 
} 

Ahora, el resto de su código sólo manipula los datos dentro de los objetos amigables struct, rápido y sólo pide el paquete/analizar cuando se tiene que interactuar con un flujo de bytes. No hay necesidad de ntoh o hton, y no bitfields para ralentizar su código.

+0

Funciona este código incluso para pasar una estructura a través de sockets >>> – codingfreak

+0

Es expresamente bueno para sockets, especialmente cuando no quiere hacer afirmaciones sobre el endianness/bus width/alignment de los procesos en cualquier extremo del socket . –

+0

Estoy totalmente de acuerdo con sus comentarios, pero el código en sí debería haber sido más explícito al respecto. La parte donde se convierten los bytes sin procesar en una palabra se debe hacer usando una instancia de algún convertidor Endian, de modo que se pueda cambiar fácilmente con una implementación diferente cuando sea necesario. – Groo