Извлечение значений через границы байтов с произвольными битовыми позициями и длинами в C # - PullRequest
9 голосов
/ 11 июля 2011

В настоящее время я работаю над сетевым инструментом, который должен декодировать / кодировать определенный протокол, который упаковывает поля в плотные битовые массивы в произвольных позициях.Например, одна часть протокола использует 3 байта для представления ряда различных полей:

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

Как видите, некоторые из полей занимают несколько байтов.Многие (большинство) также короче встроенного типа, который может использоваться для их представления, например, первое поле int длиной всего 5 бит.В этих случаях наиболее значимые биты целевого типа (такие как Int32 или Int16) должны быть дополнены 0, чтобы компенсировать разницу.

Моя проблема в том, что у меня возникают трудности с обработкой такого родаданных.В частности, мне трудно разобраться, как эффективно получить битовые массивы произвольной длины, заполнить их соответствующими битами из исходного буфера, заполнить их в соответствии с целевым типом и преобразовать битовые массивы с дополнениями в целевой тип.В идеальном мире я мог бы взять байт [3] в приведенном выше примере и вызвать метод, подобный GetInt32(byte[] bytes, int startBit, int length).

Самая близкая вещь в дикой природе, которую я нашел, это BitStream класс, но, похоже, он хочет, чтобы отдельные значения располагались на границах байтов / слов (а соглашение о полупотоковом / полуиндексированном доступе класса делает его немного запутанным).

Моей первой попыткой было использование класса BitArray , но это оказалось несколько громоздким.Достаточно просто собрать все биты из буфера в большой BitArray, перенести только те, которые вы хотите из исходного BitArray, в новый временный BitArray, а затем преобразовать это в целевое значение ... ноэто кажется неправильным и отнимает много времени.

Сейчас я рассматриваю класс, подобный следующему, который ссылается (или создает) буфер исходного / целевого байта [] вместе со смещением и предоставляет методы get и set для определенныхцелевые типы.Сложность в том, что получение / установка значений могут занимать несколько байтов.

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
}

Я ищу любые рекомендации в этой области, такие как сторонние библиотеки, алгоритмы получения / установки значений в произвольных битовых позициях, которыеохват нескольких байтов, отзывы о моем подходе и т. д. Я включил приведенный выше класс для пояснения и не обязательно ищу код для его заполнения (хотя я не буду спорить, если кто-то захочет его решить!).

Ответы [ 4 ]

1 голос
/ 21 июля 2011

Как и было обещано, вот класс, который я в итоге создал для этой цели.Он обернет произвольный байтовый массив по заданному индексу и разрешит чтение / запись на уровне битов.Он предоставляет методы для чтения / записи произвольных блоков битов из других байтовых массивов или для чтения / записи примитивных значений с заданными пользователем смещениями и длинами.Это очень хорошо подходит для моей ситуации и решает точный вопрос, который я задал выше.Однако у него есть пара недостатков.Во-первых, это явно не очень документировано - у меня просто не было времени.Во-вторых, нет никаких границ или других проверок.В настоящее время также требуется библиотека MiscUtil для преобразования в обратный порядок.Все это говорит, надеюсь, что это может помочь решить или послужить отправной точкой для кого-то с аналогичным вариантом использования.

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);
    }
}
1 голос
/ 11 июля 2011

Если ваши пакеты всегда меньше 8 или 4 байтов, было бы легче хранить каждый пакет в Int32 или Int64.Массив байтов только усложняет вещи.Вы должны обратить внимание на хранилище High-Endian против Low-Endian.

А затем, для 3-байтового пакета:

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 голосов
/ 11 июля 2011

В чем проблема с использованием простых битовых сдвигов для получения значений?

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

Эта - довольно хорошая статья, освещающая эту тему.это в C, но те же понятия все еще применяются.

0 голосов
/ 11 июля 2011

Сначала кажется, что вы изобрели колесо с классом System.Collections.BitArray .Что касается фактического нахождения значения определенного поля битов, я думаю, что это можно легко сделать с помощью небольшой математической магии следующего псевдокода:

  1. Начать с самой отдаленной цифры в вашем выборе (startIndex+ длина).
  2. Если установлено, добавьте 2 ^ (расстояние от цифры).В этом случае это будет 0 (mostDistance - self = 0).Так что добавьте 2 ^ 0 (1).
  3. Переместить один бит влево.
  4. Повторить для каждой цифры нужной длины.

В этой ситуации, если бы у вас был битовый массив, подобный так:

10001010

И вы хотите, чтобы значение цифр 0-3, вы бы получили что-токак:

[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

Так как 1000 (двоичный) == 8, математика работает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...