Filling an octet string

后端 未结 1 1129
感情败类
感情败类 2021-01-23 10:13

I have 65 parameters of different bit length which I need to fill in an octet string. Parametrs would be filled continuously in octet string. For example, suppose the first para

相关标签:
1条回答
  • 2021-01-23 10:44

    You are lucky. Since I love bit twiddling, I wrote a generic implementation of a BitBuffer just for you. I have not tested it thoroughly (e.g. not all nasty corner cases) but as you will see, it passes the simple tests I added to the code below.

    #include <assert.h>
    #include <stdint.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdbool.h>
    
    struct BitBuffer {
        unsigned length;       // No of bits used in buffer
        unsigned capacity;     // No of bits available in buffer
        uint8_t buffer[];
    };
    
    struct BitBuffer * newBitBuffer (
        unsigned capacityInBits
    ) {
        int capacityInBytes;
        struct BitBuffer * result;
    
        capacityInBytes = (capacityInBits / 8);
        if (capacityInBits % 8 != 0) {
            capacityInBytes++;
        }
    
        result = malloc(sizeof(*result) + capacityInBytes);
        if (result) {
            result->length = 0;
            result->capacity = capacityInBits;
        }
        return result;
    }
    
    bool addBitsToBuffer (
        struct BitBuffer * bbuffer, const void * bits, unsigned bitCount
    ) {
        unsigned tmpBuf;
        unsigned tmpBufLen;
        unsigned tmpBufMask;
        uint8_t * nextBufBytePtr;
        const uint8_t * nextBitsBytePtr;
    
        // Verify input parameters are sane
        if (!bbuffer || !bits) {
            // Evil!
            return false;
        }
        if (bitCount == 0) {
            // No data to add? Nothing to do.
            return true;
        }
    
        // Verify we have enough space available
        if (bbuffer->length + bitCount > bbuffer->capacity) {
            // Won't fit!
            return false;
        }
    
        // Get the first byte we start writing bits to
        nextBufBytePtr = bbuffer->buffer + (bbuffer->length / 8);
    
        // Shortcut:
        // If we happen to be at a byte boundary,
        // we can simply use memcpy and save us a lot of headache.
        if (bbuffer->length % 8 == 0) {
            unsigned byteCount;
    
            byteCount = bitCount / 8;
            if (bitCount % 8 != 0) {
                byteCount++;
            }
            memcpy(nextBufBytePtr, bits, byteCount);
            bbuffer->length += bitCount;
            return true;
        }
    
        // Let the bit twiddling begin
        nextBitsBytePtr = bits;
        tmpBuf = *nextBufBytePtr;
        tmpBufLen = bbuffer->length % 8;
        tmpBuf >>= 8 - tmpBufLen;
        tmpBufMask = (~0u) >> ((sizeof(unsigned) * 8) - tmpBufLen);
        // tmpBufMask has the first tmpBufLen bits set to 1.
        // E.g. "tmpBufLen == 3" ==> "tmpBufMask == 0b111 (7)"
        // or "tmpBufLen == 6" ==> "tmpBufMask = 0b111111 (63)", and so on.
    
        // Beyond this point we will neither access bbuffer->length again, nor
        // can this function fail anymore, so we set the final length already.
        bbuffer->length += bitCount;
    
        // Process input bits in byte chunks as long as possible
        while (bitCount >= 8) {
            // Add 8 bits to tmpBuf
            tmpBuf = (tmpBuf << 8) | *nextBitsBytePtr;
            // tmpBuf now has "8 + tmpBufLen" bits set
    
            // Add the highest 8 bits of tmpBuf to our BitBuffer
            *nextBufBytePtr = (uint8_t)(tmpBuf >> tmpBufLen);
    
            // Cut off the highest 8 bits of tmpBuf
            tmpBuf &= tmpBufMask;
            // tmpBuf now has tmpBufLen bits set again
    
            // Skip to next input/output byte
            bitCount -= 8;
            nextBufBytePtr++;
            nextBitsBytePtr++;
        }
    
        // Test if we still have bits left. That will be the case
        // if the input bit count was no integral multiple of 8.
        if (bitCount != 0) {
            // Add bitCount bits to tmpBuf
            tmpBuf = (tmpBuf << bitCount) | (*nextBitsBytePtr >> (8 - bitCount));
            tmpBufLen += bitCount;
        }
    
        // tmpBufLen is never 0 here, it must have a value in the range [1, 14].
        // We add zero bits to it so that tmpBuf has 16 bits set.
        tmpBuf <<= (16 - tmpBufLen);
    
        // Now we only need to add one or two more bytes from tmpBuf to our
        // BitBuffer, depending on its length prior to adding the zero bits.
        *nextBufBytePtr = (uint8_t)(tmpBuf >> 8);
        if (tmpBufLen > 8) {
            *(++nextBufBytePtr) = (uint8_t)(tmpBuf & 0xFF);
        }
        return true;
    }
    
    
    
    int main ()
    {
        bool res;
        uint8_t testData[4];
        struct BitBuffer * buf;
    
        buf = newBitBuffer(1024); // Can hold up to 1024 bits
        assert(buf);
    
        // Let's add some test data.
    
        // Add 1 bit "1" => Buffer "1"
        testData[0] = 0xFF;
        res = addBitsToBuffer(buf, testData, 1);
        assert(res);
    
        // Add 6 bits "0101 01" => Buffer "1010 101"
        testData[0] = 0x54;
        res = addBitsToBuffer(buf, testData, 6);
        assert(res);
    
        // Add 4 Bits "1100" => Buffer "1010 1011 100"
        testData[0] = 0xC0;
        res = addBitsToBuffer(buf, testData, 4);
        assert(res);
    
        // Add 16 Bits "0111 1010 0011 0110"
        // ==> Buffer "1010 1011 1000 1111 0100 0110 110
        testData[0] = 0x7A;
        testData[1] = 0x36;
        res = addBitsToBuffer(buf, testData, 16);
        assert(res);
    
        // Add 5 Bits "0001 1"
        // ==> Buffer "1010 1011 1000 1111 0100 0110 1100 0011"
        testData[0] = 0x18;
        res = addBitsToBuffer(buf, testData, 5);
        assert(res);
    
        // Buffer should now have exactly a length of 32 bits
        assert(buf->length == 32);
    
        // And it should have the value 0xAB8F46C3
        testData[0] = 0xAB;
        testData[1] = 0x8F;
        testData[2] = 0x46;
        testData[3] = 0xC3;
        assert(memcmp(buf->buffer, testData, 4) == 0);
    
        free(buf);
        return 0;
    }
    

    The code is not optimized for maximum performance, yet I guess it should have a decent performance nonetheless. Any additional performance tweaks would have increased the code size noticeably and I wanted to keep the code rather simple. Some people may argue that using >> 3 instead of / 8 and & 0x7 instead of % 8 will lead to better performance, yet if you use a decent C compiler, that's exactly what the compiler will internally do anyway if you enable optimizations and thus I preferred to keep the code more readable instead.

    Additional Note
    When you pass pointers to multi-byte data types, watch the byte order! E.g. the following code

     uint16_t x16 = ...;
     addBitsToBuffer(buf, &x16, ...);
     uint32_t x32 = ...;
     addBitsToBuffer(buf, &x32, ...);
    

    works fine on a big endian machine (PPC CPU), yet it may not give the expected results on a little endian machine (e.g. x86 CPU). On a little endian machine you would have to swap the byte order first. You can use htons and htonl for this purpose:

     uint16_t x16 = ...;
     uint16_t x16be = htons(x16);
     addBitsToBuffer(buf, &x16be, ...);
     uint32_t x32 = ...;
     uint32_t x32be = htonl(x32);
     addBitsToBuffer(buf, &x32be, ...);
    

    On a big endian machine, htonX functions/macros usually do nothing, since the value is already in "network byte order" (big endian), while on a little endian machine they will swap the byte order.

    Passing an uint8_t pointer will always work on either machine, it is only a single byte, hence there is no byte order.

    0 讨论(0)
提交回复
热议问题