'is it possible to do memcpy in bits instead of bytes?

I would like to know is it possible to do memcpy by bits instead of bytes?

I am writing a C code for Ethernet frame with VLAN tagging, in which I need to fill different values for VLAN header attributes (PCP-3bits,DEI-1bit,VID-12bits).

How can I do memcpy to those bits, or any other possibility to fill values to those attributes in bits.

Thanks in advance !



Solution 1:[1]

No. Bits are not addressable (meaning that it is not possible to read them and only them directly from memory. They have no address. Only bytes have addresses).

You need to read the byte or word that contains the bits you are interested in and do the masking yourself.

Solution 2:[2]

I have done the 802.1Q VLAN tagging of frames for a client where they had only 802.3 Ethernet frames but wanted to migrate to 802.1Q as there was a new VLAN aware switch installed.

Firstly, you cannot copy bits. We had copied the tag in bytes using memcpy.

Illustration (refer Wikipedia for descriptions of fields):-

VLAN Tag = 4 Bytes; consisting of TPID(2 Bytes) and TCI(2 Bytes).

TPID is simple and is always 0x8100 indicating a VLAN tagged frame.

TCI consists of PCP-3bits, DEI-1bit, VID-12bits. Breakdown the TCI to nibbles i.e. 4-bits. By default, the nibble(PCP+DEI) = 0x0 assuming priority is disabled and DEI = 0. The remaining 3-nibbles(12bits) are for the VLAN-ID itself. Say, you want to tag a frame for VLAN-ID = 123. In hex this will be = 0x07B.

Group the nibbles together and there you have your 2 byte TCI field which can now be seen as 0x007B.

Then you can do the below. (code is not compiled)

unsigned short int vlanTPID, vlanTCI;
unsigned char     *dest, *src;

// Set the VLAN Tag
vlanTPID = 0x8100;
vlanTCI  = 0x007B;

// Pointer to the TPID position of ethernet frame
dest = &vlanTagPosition;
src = &vlanTPID;
memcpy(dest, src, sizeof(vlanTPID));

// Increment dest pointer by 2 bytes to insert TCI in the ethernet frame
dest += 2;
src = &vlanTCI;
memcpy(dest, src, sizeof(vlanTCI));

Solution 3:[3]

If you need to fill fields, you can use C bit-fields with a struct, like this:

struct box_props {
    unsigned first  : 1;
    unsigned second : 3;
    unsigned : 4;
};

Where 1, for instance, means that the field is 1bit long. The last (unnamed) field means: 4bit padding.

Define struct, memcpy to it and read fields as if they where unsigned. Same for writing.

NOTE: always pad to integer byte, or memcpy could have unwanted effects.

Solution 4:[4]

Just implementation to copy any arbitrary bitset. The last implementation is a part of the tacklelib library:
https://github.com/andry81/tacklelib
https://github.com/andry81/tacklelib/blob/trunk/include/tacklelib/utility/memory.hpp

Pros:

  • Copies by 64-bit words.
  • Dynamically use memcpy function when it is possible to deduce to it.

Cons:

  • The both buffers must be padded for 7 bytes at the end and so 7 bytes in the output buffer after the end offset basically can be overwritten. If these bytes is not a padded buffer end, then they can be restored by saving this part separately into 64-bit word plus mask and copy back after the function call.
  • Buffers must not overlap.
// Bitwise memory copy.
// Both buffers must be padded to 7 bytes remainder to be able to read/write the last 8-bit block as 64-bit block.
// Buffers must not overlap.
//
inline void memcpy_bitwise64(uint8_t * to_padded_int64_buf, uint64_t to_first_bit_offset, uint8_t * from_padded_int64_buf, uint64_t from_first_bit_offset, uint64_t bit_size)
{
    assert(bit_size);

    uint64_t bit_offset = 0;

    uint32_t from_byte_offset = uint32_t(from_first_bit_offset / 8);
    uint32_t to_byte_offset = uint32_t(to_first_bit_offset / 8);

    uint32_t remainder_from_bit_offset = uint32_t(from_first_bit_offset % 8);
    uint32_t remainder_to_bit_offset = uint32_t(to_first_bit_offset % 8);

    while (bit_offset < bit_size) {
        if (remainder_to_bit_offset >= remainder_from_bit_offset && (remainder_to_bit_offset || remainder_from_bit_offset)) {
            const uint64_t from_bit_block = *(uint64_t *)&from_padded_int64_buf[from_byte_offset];
            uint64_t & to_bit_block = *(uint64_t *)&to_padded_int64_buf[to_byte_offset];

            const uint32_t to_first_bit_delta_offset = remainder_to_bit_offset - remainder_from_bit_offset;
            const uint64_t to_bit_block_inversed_mask = uint64_t(~0) << remainder_to_bit_offset;

            to_bit_block = ((from_bit_block << to_first_bit_delta_offset) & to_bit_block_inversed_mask) | (to_bit_block & ~to_bit_block_inversed_mask);

            const uint32_t bit_size_copied = 64 - remainder_to_bit_offset;

            bit_offset += bit_size_copied;

            from_first_bit_offset += bit_size_copied;
            to_first_bit_offset += bit_size_copied;

            if (remainder_to_bit_offset != remainder_from_bit_offset) {
                from_byte_offset += 7;
                to_byte_offset += 8;

                remainder_from_bit_offset = 8 - to_first_bit_delta_offset;
                remainder_to_bit_offset = 0;
            }
            else {
                from_byte_offset += 8;
                to_byte_offset += 8;

                remainder_from_bit_offset = 0;
                remainder_to_bit_offset = 0;
            }
        }
        else if (remainder_to_bit_offset < remainder_from_bit_offset) {
            const uint64_t from_bit_block = *(uint64_t *)&from_padded_int64_buf[from_byte_offset];
            uint64_t & to_bit_block = *(uint64_t *)&to_padded_int64_buf[to_byte_offset];

            const uint32_t to_first_bit_delta_offset = remainder_from_bit_offset - remainder_to_bit_offset;
            const uint64_t to_bit_block_inversed_mask = uint64_t(~0) << remainder_to_bit_offset;

            to_bit_block = ((from_bit_block >> to_first_bit_delta_offset) & to_bit_block_inversed_mask) | (to_bit_block & ~to_bit_block_inversed_mask);

            const uint32_t bit_size_copied = 64 - remainder_from_bit_offset;

            bit_offset += bit_size_copied;

            from_first_bit_offset += bit_size_copied;
            to_first_bit_offset += bit_size_copied;

            from_byte_offset += 8;
            to_byte_offset += 7;

            remainder_from_bit_offset = 0;
            remainder_to_bit_offset = (8 - to_first_bit_delta_offset);
        }
        // optimization
        else {
            const uint64_t bit_size_remain = bit_size - bit_offset;
            const uint32_t byte_size_remain = uint32_t(bit_size_remain / 8);

            if (byte_size_remain + 1 > 8) {
                memcpy(to_padded_int64_buf + to_byte_offset, from_padded_int64_buf + from_byte_offset, byte_size_remain + 1);
            }
            // optimization
            else {
                *(uint64_t *)&to_padded_int64_buf[to_byte_offset] = *(uint64_t *)&from_padded_int64_buf[from_byte_offset];
            }

            break;
        }

        assert(from_byte_offset == uint32_t(from_first_bit_offset / 8));
        assert(remainder_from_bit_offset == uint32_t(from_first_bit_offset % 8));

        assert(to_byte_offset == uint32_t(to_first_bit_offset / 8));
        assert(remainder_to_bit_offset == uint32_t(to_first_bit_offset % 8));
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Pascal Cuoq
Solution 2
Solution 3
Solution 4