2011-07-11 18 views
9

Actualmente estoy trabajando en una herramienta de red que necesita decodificar/codificar un protocolo particular que empaqueta campos en matrices de bits densa en posiciones arbitrarias. Por ejemplo, una parte del protocolo utiliza 3 bytes para representar un número de diferentes campos:Extrayendo valores a través de límites de bytes con posiciones y longitudes de bits arbitrarios en C#

Bit Position(s) Length (In Bits) Type 
0    1     bool 
1-5    5     int 
6-13    8     int 
14-22   9     uint 
23    1     bool 

Como se puede ver, varios de los campos abarcan múltiples bytes. Muchos (la mayoría) también son más cortos que el tipo incorporado que podría usarse para representarlos, como el primer campo int que tiene solo 5 bits de longitud. En estos casos, los bits más significativos del tipo de destino (como un Int32 o Int16) deben rellenarse con 0 para compensar la diferencia.

Mi problema es que estoy teniendo dificultades para procesar este tipo de datos. Específicamente, me está resultando difícil descifrar cómo obtener de manera eficiente matrices de bits de longitud arbitraria, rellenarlas con los bits apropiados del buffer de origen, rellenarlas para que coincidan con el tipo de destino y convertir las matrices de bits rellenados al tipo de destino. En un mundo ideal, podría tomar el byte [3] en el ejemplo anterior y llamar a un método como GetInt32(byte[] bytes, int startBit, int length).

Lo más parecido en la naturaleza que he encontrado es una clase BitStream, pero parece querer que los valores individuales se alineen en los límites de bytes/palabras (y la convención de acceso a mitad de la transmisión/indexado de la clase lo hace un poco confuso).

Mi primer intento fue utilizar la clase BitArray, pero resultó algo difícil de manejar. Es bastante fácil rellenar todos los bits del búfer en un gran BitArray, transferir solo los que desee de la fuente BitArray a un nuevo BitArray temporal, y luego convertir eso en el valor objetivo ... pero parece incorrecto, y muy pérdida de tiempo.

Ahora estoy considerando una clase como la siguiente que hace referencia (o crea) un buffer de origen/destino byte [] junto con un desplazamiento y proporciona métodos get y set para ciertos tipos de destino. La parte difícil es que obtener/establecer valores puede abarcar varios bytes.

class BitField 
{ 
    private readonly byte[] _bytes; 
    private readonly int _offset; 

    public BitField(byte[] bytes) 
     : this(bytes, 0) 
    { 
    } 

    public BitField(byte[] bytes, int offset) 
    { 
     _bytes = bytes; 
     _offset = offset; 
    } 

    public BitField(int size) 
     : this(new byte[size], 0) 
    { 
    } 

    public bool this[int bit] 
    { 
     get { return IsSet(bit); } 
     set { if (value) Set(bit); else Clear(bit); } 
    } 

    public bool IsSet(int bit) 
    { 
     return (_bytes[_offset + (bit/8)] & (1 << (bit % 8))) != 0; 
    } 

    public void Set(int bit) 
    { 
     _bytes[_offset + (bit/8)] |= unchecked((byte)(1 << (bit % 8))); 
    } 

    public void Clear(int bit) 
    { 
     _bytes[_offset + (bit/8)] &= unchecked((byte)~(1 << (bit % 8))); 
    } 

    //startIndex = the index of the bit at which to start fetching the value 
    //length = the number of bits to include - may be less than 32 in which case 
    //the most significant bits of the target type should be padded with 0 
    public int GetInt32(int startIndex, int length) 
    { 
     //NEED CODE HERE 
    } 

    //startIndex = the index of the bit at which to start storing the value 
    //length = the number of bits to use, if less than the number of bits required 
    //for the source type, precision may be lost 
    //value = the value to store 
    public void SetValue(int startIndex, int length, int value) 
    { 
     //NEED CODE HERE 
    } 

    //Other Get.../Set... methods go here 
} 

Busco a ningún tipo de orientación en esta área, tales como bibliotecas de terceros, los algoritmos para obtener/establecer los valores en las posiciones de bits arbitrarios que abarcan múltiples bytes, comentarios sobre mi enfoque, etc. incluyó la clase anterior para aclaración y no estoy necesariamente buscando el código para completarlo (¡aunque no discutiré si alguien quiere resolverlo!).

+0

Algunas personas han mencionado endianness, y eso es ciertamente una preocupación. Como estoy tratando con datos de red, supongo que el buffer original está en big-endian (o en orden de red). Estaba planeando usar el excelente 'EndianBitConverter' de [MiscUtils] (http://www.yoda.arachsys.com/csharp/miscutil/) para convertir todo al orden local dentro de los métodos de Obtener ... – daveaglick

+0

Todavía estoy trabajando en este problema. Ninguna de las respuestas a continuación era correcta para esta situación (en particular, los aspectos que implican posiciones arbitrarias y límites de byte que abarcan). He decidido implementar completamente la clase BitField presentada en la pregunta y he tenido algo de suerte. Debido a que puede tener muchos usos, especialmente para el procesamiento de red donde los campos de bits suelen estar empaquetados densamente, publicaré la clase completada en los próximos días una vez que se haya realizado como respuesta adicional. Seguiré votando otras respuestas útiles que aborden la pregunta. – daveaglick

Respuesta

1

Según lo prometido, aquí es la clase que terminé creando para este fin. Envolverá una matriz de bytes arbitraria en un índice opcionalmente especificado y permitirá la lectura/escritura en el nivel de bits. Proporciona métodos para leer/escribir bloques arbitrarios de bits de otras matrices de bytes o para leer/escribir valores primitivos con desplazamientos y longitudes definidos por el usuario. Funciona muy bien para mi situación y resuelve la pregunta exacta que hice anteriormente. Sin embargo, tiene algunas deficiencias. La primera es que obviamente no está muy documentada, simplemente no he tenido el tiempo. El segundo es que no hay límites u otros controles. También actualmente requiere que la biblioteca MiscUtil proporcione la conversión endian. Dicho todo esto, con suerte, esto puede ayudar a resolver o servir como punto de partida para otra persona con un caso de uso similar.

internal class BitField 
{ 
    private readonly byte[] _bytes; 
    private readonly int _offset; 
    private EndianBitConverter _bitConverter = EndianBitConverter.Big; 

    public BitField(byte[] bytes) 
     : this(bytes, 0) 
    { 
    } 

    //offset = the offset (in bytes) into the wrapped byte array 
    public BitField(byte[] bytes, int offset) 
    { 
     _bytes = bytes; 
     _offset = offset; 
    } 

    public BitField(int size) 
     : this(new byte[size], 0) 
    { 
    } 

    //fill == true = initially set all bits to 1 
    public BitField(int size, bool fill) 
     : this(new byte[size], 0) 
    { 
     if (!fill) return; 
     for(int i = 0 ; i < size ; i++) 
     { 
      _bytes[i] = 0xff; 
     } 
    } 

    public byte[] Bytes 
    { 
     get { return _bytes; } 
    } 

    public int Offset 
    { 
     get { return _offset; } 
    } 

    public EndianBitConverter BitConverter 
    { 
     get { return _bitConverter; } 
     set { _bitConverter = value; } 
    } 

    public bool this[int bit] 
    { 
     get { return IsBitSet(bit); } 
     set { if (value) SetBit(bit); else ClearBit(bit); } 
    } 

    public bool IsBitSet(int bit) 
    { 
     return (_bytes[_offset + (bit/8)] & (1 << (7 - (bit % 8)))) != 0; 
    } 

    public void SetBit(int bit) 
    { 
     _bytes[_offset + (bit/8)] |= unchecked((byte)(1 << (7 - (bit % 8)))); 
    } 

    public void ClearBit(int bit) 
    { 
     _bytes[_offset + (bit/8)] &= unchecked((byte)~(1 << (7 - (bit % 8)))); 
    } 

    //index = the index of the source BitField at which to start getting bits 
    //length = the number of bits to get 
    //size = the total number of bytes required (0 for arbitrary length return array) 
    //fill == true = set all padding bits to 1 
    public byte[] GetBytes(int index, int length, int size, bool fill) 
    { 
     if(size == 0) size = (length + 7)/8; 
     BitField bitField = new BitField(size, fill); 
     for(int s = index, d = (size * 8) - length ; s < index + length && d < (size * 8) ; s++, d++) 
     { 
      bitField[d] = IsBitSet(s); 
     } 
     return bitField._bytes; 
    } 

    public byte[] GetBytes(int index, int length, int size) 
    { 
     return GetBytes(index, length, size, false); 
    } 

    public byte[] GetBytes(int index, int length) 
    { 
     return GetBytes(index, length, 0, false); 
    } 

    //bytesIndex = the index (in bits) into the bytes array at which to start copying 
    //index = the index (in bits) in this BitField at which to put the value 
    //length = the number of bits to copy from the bytes array 
    public void SetBytes(byte[] bytes, int bytesIndex, int index, int length) 
    { 
     BitField bitField = new BitField(bytes); 
     for (int i = 0; i < length; i++) 
     { 
      this[index + i] = bitField[bytesIndex + i]; 
     } 
    } 

    public void SetBytes(byte[] bytes, int index, int length) 
    { 
     SetBytes(bytes, 0, index, length); 
    } 

    public void SetBytes(byte[] bytes, int index) 
    { 
     SetBytes(bytes, 0, index, bytes.Length * 8); 
    } 

    //UInt16 

    //index = the index (in bits) at which to start getting the value 
    //length = the number of bits to use for the value, if less than required the value is padded with 0 
    public ushort GetUInt16(int index, int length) 
    { 
     return _bitConverter.ToUInt16(GetBytes(index, length, 2), 0); 
    } 

    public ushort GetUInt16(int index) 
    { 
     return GetUInt16(index, 16); 
    } 

    //valueIndex = the index (in bits) of the value at which to start copying 
    //index = the index (in bits) in this BitField at which to put the value 
    //length = the number of bits to copy from the value 
    public void Set(ushort value, int valueIndex, int index, int length) 
    { 
     SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length); 
    } 

    public void Set(ushort value, int index) 
    { 
     Set(value, 0, index, 16); 
    } 

    //UInt32 

    public uint GetUInt32(int index, int length) 
    { 
     return _bitConverter.ToUInt32(GetBytes(index, length, 4), 0); 
    } 

    public uint GetUInt32(int index) 
    { 
     return GetUInt32(index, 32); 
    } 

    public void Set(uint value, int valueIndex, int index, int length) 
    { 
     SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length); 
    } 

    public void Set(uint value, int index) 
    { 
     Set(value, 0, index, 32); 
    } 

    //UInt64 

    public ulong GetUInt64(int index, int length) 
    { 
     return _bitConverter.ToUInt64(GetBytes(index, length, 8), 0); 
    } 

    public ulong GetUInt64(int index) 
    { 
     return GetUInt64(index, 64); 
    } 

    public void Set(ulong value, int valueIndex, int index, int length) 
    { 
     SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length); 
    } 

    public void Set(ulong value, int index) 
    { 
     Set(value, 0, index, 64); 
    } 

    //Int16 

    public short GetInt16(int index, int length) 
    { 
     return _bitConverter.ToInt16(GetBytes(index, length, 2, IsBitSet(index)), 0); 
    } 

    public short GetInt16(int index) 
    { 
     return GetInt16(index, 16); 
    } 

    public void Set(short value, int valueIndex, int index, int length) 
    { 
     SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length); 
    } 

    public void Set(short value, int index) 
    { 
     Set(value, 0, index, 16); 
    } 

    //Int32 

    public int GetInt32(int index, int length) 
    { 
     return _bitConverter.ToInt32(GetBytes(index, length, 4, IsBitSet(index)), 0); 
    } 

    public int GetInt32(int index) 
    { 
     return GetInt32(index, 32); 
    } 

    public void Set(int value, int valueIndex, int index, int length) 
    { 
     SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length); 
    } 

    public void Set(int value, int index) 
    { 
     Set(value, 0, index, 32); 
    } 

    //Int64 

    public long GetInt64(int index, int length) 
    { 
     return _bitConverter.ToInt64(GetBytes(index, length, 8, IsBitSet(index)), 0); 
    } 

    public long GetInt64(int index) 
    { 
     return GetInt64(index, 64); 
    } 

    public void Set(long value, int valueIndex, int index, int length) 
    { 
     SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length); 
    } 

    public void Set(long value, int index) 
    { 
     Set(value, 0, index, 64); 
    } 

    //Char 

    public char GetChar(int index, int length) 
    { 
     return _bitConverter.ToChar(GetBytes(index, length, 2), 0); 
    } 

    public char GetChar(int index) 
    { 
     return GetChar(index, 16); 
    } 

    public void Set(char value, int valueIndex, int index, int length) 
    { 
     SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length); 
    } 

    public void Set(char value, int index) 
    { 
     Set(value, 0, index, 16); 
    } 

    //Bool 

    public bool GetBool(int index, int length) 
    { 
     return _bitConverter.ToBoolean(GetBytes(index, length, 1), 0); 
    } 

    public bool GetBool(int index) 
    { 
     return GetBool(index, 8); 
    } 

    public void Set(bool value, int valueIndex, int index, int length) 
    { 
     SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length); 
    } 

    public void Set(bool value, int index) 
    { 
     Set(value, 0, index, 8); 
    } 

    //Single and double precision floating point values must always use the correct number of bits 
    public float GetSingle(int index) 
    { 
     return _bitConverter.ToSingle(GetBytes(index, 32, 4), 0); 
    } 

    public void SetSingle(float value, int index) 
    { 
     SetBytes(_bitConverter.GetBytes(value), 0, index, 32); 
    } 

    public double GetDouble(int index) 
    { 
     return _bitConverter.ToDouble(GetBytes(index, 64, 8), 0); 
    } 

    public void SetDouble(double value, int index) 
    { 
     SetBytes(_bitConverter.GetBytes(value), 0, index, 64); 
    } 
} 
+0

¿Podría publicar esto como una edición de su pregunta original? Lo hace más fácil de encontrar. ¡También gracias! – Benjamin

0

En primer lugar, parece que has inventado la rueda con la clase System.Collections.BitArray. En cuanto a encontrar realmente el valor de un campo específico de bits, creo que se puede lograr fácilmente con un poco de magia matemática de la siguiente pseudocódigo:

  1. de inicio en el dígito más distante en su selección (longitud startIndex +) .
  2. Si está configurado, agregue 2^(distancia del dígito). En este caso, sería 0 (mostDistance - self = 0). Entonces agregue 2^0 (1).
  3. Mueva un bit hacia la izquierda.
  4. Repita para cada dígito en la longitud que desee.

En esa situación, si has tenido una matriz de bits, así:

Y desea que el valor de los dígitos 0-3, se llega a algo como:

[Index 3] [Index 2] [Index 1] [Index 0] 
(3 - 3)  (3 - 2)  (3 - 1)  (3 - 0) 
============================================= 
(0 * 2^0) + (0 * 2^1) + (0 * 2^2) + (1 * 2^3) = 8 

Desde 1000 (binario) == 8, la matemática funciona.

0

¿Cuál es el problema con el simple uso de cambios de bit simples para obtener sus valores?

int data = Convert.ToInt32("110000000010000000100001", 2); 

bool v1 = (data & 1) == 1; // True 
int v2 = (data >> 1) & 0x1F; // 16 
int v3 = (data >> 6) & 0xFF; // 128 
uint v4 = (uint)(data >> 14) & 0x1FF; // 256 
bool v5 = (data >> 23) == 1; // True 

This es un muy buen artículo sobre el tema. está en C, pero los mismos conceptos aún se aplican.

+0

Para mi problema específico, hay muchos de estos campos y no estoy seguro de querer codificar accedores y adaptadores para cada uno a mano. De hecho, en algunos casos, es posible que no conozca los patrones exactamente hasta que se decodifiquen otras partes. Creo que una solución general al problema sería útil en muchos casos. No me opongo al uso de la manipulación de bits, máscaras, cambios, etc. - Me gustaría que los algoritmos me permitan hacerlo utilizando puntos de partida y longitudes arbitrarios. – daveaglick

1

Si sus paquetes son siempre menores de 8 o 4 bytes, sería más fácil almacenar cada paquete en un Int32 o Int64. La matriz de bytes solo complica las cosas. Tienes que prestar atención al almacenamiento High-Endian vs Low-Endian.

Y luego, para un paquete de 3 bytes:

public static void SetValue(Int32 message, int startIndex, int length, int value) 
{ 
    // we want lengthx1 
    int mask = (1 << length) - 1;  
    value = value & mask; // or check and throw 

    int offset = 24 - startIndex - length; // 24 = 3 * 8 
    message = message | (value << offset); 
} 
+0

Lamentablemente, la longitud de un paquete varía ampliamente y suele ser mucho más grande de lo que podría soportar un solo Int64. He visto la nueva estructura de [BigInteger] (http://msdn.microsoft.com/en-us/library/system.numerics.biginteger.aspx) y me pregunté si podría funcionar con los mismos conceptos que presenta, pero Estoy estancado en 3.5 por ahora, así que no fui demasiado lejos en ese camino.Quizás necesito echar otro vistazo: ¿funcionarían estos algoritmos en un 'BigInteger' (no admite operadores bit a bit)? – daveaglick

+0

Bueno, BigInteger parece tener operadores de turno, podría ahorrarte algo de trabajo. Pero no esperes velocidad ardiente. –

Cuestiones relacionadas