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.
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
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
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. –