/** * @file * Raw memory related functionality * * @copyright * @verbatim Copyright @ 2017 Audi Electronics Venture GmbH. All rights reserved. This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. @endverbatim */ #ifndef A_UTILS_UTIL_MEMORY_BITSERIALIZER_INCLUDED #define A_UTILS_UTIL_MEMORY_BITSERIALIZER_INCLUDED #include #include "a_util/memory.h" #include "a_util/result.h" namespace a_util { namespace memory { //define all needed error types and values locally _MAKE_RESULT(-4, ERR_POINTER); _MAKE_RESULT(-5, ERR_INVALID_ARG); /// Enum describing the endianess typedef enum { bit_little_endian = 1, bit_big_endian = 2, } Endianess; /** * Returns the endianess of the platform * @return See \ref Endianess */ Endianess get_platform_endianess(); namespace detail { /** * Format the bit pattern of a uint64_t value to a string * Used for debug purposes. * * @param [in] value The value to print. * @retval The bitstring */ std::string formatBits(uint64_t value); /** * Convert the endianess of a signal by correctly swapping the byte order if required. * The variable signal is a uint64_t, but the value that needs conversion might be smaller. * The parameter bit_length determines if a 16, 32 or 64 value should be swapped. * For a LE system, reading BE signals of 3, 5, 6 or 7 bytes length the value will be aligned * within a 4 or 8 byte value before swapping bytes. * * @param [in,out] signal Pointer to the variable to store the read value in. * @param [in] endianess Parameter describing the endianess of the bitfield to convert. * @param [in] bit_length Number of bits to read. * * @return Returns a standard result code. */ a_util::result::Result convertSignalEndianess(uint64_t *signal, Endianess endianess, size_t bit_length); /** * Converter Base * Contains the base methods used by all inheriting Converter classes. */ template class ConverterBase { protected: /** * Read value from bitfield. * Operating on a uint64_t copy to allow bit shifting and masking operations. * * @param [in] buffer Pointer to the memory buffer to read from. * @param [in] start_bit Bit position to start reading from. The least significant bit * has the index 0. * @param [in] bit_length Number of bits to read. * @param [out] value Pointer to the variable to store the read value in. * @param [in] endianess Parameter describing the endianess of the bitfield to read from. * * @return Returns a standard result code. */ static a_util::result::Result readSignal(uint8_t *buffer, size_t start_bit, size_t bit_length, T* value, Endianess endianess = get_platform_endianess()) { /* * offset_end offset_start * _ ____ * | | | | * ....|...abcde|fghijklm|no......|.... Buffer (index 0 on the right end side) * |_______________| * bit_length ^ * | * start_bit */ // 1) COPY relevant bytes of Buffer content to result variable. uint64_t result = 0; // Result value uint64_t ninth_byte = 0; // variable to eventually store a ninth byte from buffer size_t bytes_to_read = 0; copyBytesFromBuffer(buffer, &result, start_bit, bit_length, &ninth_byte, &bytes_to_read); // 2) TRIM unrelevant bits and SHIFT to align value. // Number of bits the start position is offset from 0 (0 for aligned signal) size_t offset_start = start_bit % 8; // Number of bits the end position is offset from the end of the last byte (0 for complete bytes) size_t offset_end = (8 - ((start_bit + bit_length) % 8)) % 8; /********************************************************************************************** * Distinguish between LE and BE operating systems to get the shift operations right * **********************************************************************************************/ // On LE System if (get_platform_endianess() == bit_little_endian) { /* Use bit mask to remove bits on the higher end, which do not belong to the value to read. * * ...|...abcde|fghijklm|no......| => 000|000abcde|fghijklm|no......| * */ cutLeadingBits(&result, bit_length + offset_start); /* Shift right to align start at position 0 (also trims the right end). * * 000|000abcde|fghijklm|no......| => 000|00000000|0abcdefg|hijklmno| * */ result >>= offset_start; // Eventually get bits from the copied 9th byte. if (ninth_byte > 0) // nothing to take care of if nothing was copied or all copied bits are 0. { // deleteAll unwanted bits from ninth byte. cutLeadingBits(&ninth_byte, (8 - offset_end)); size_t bit_size = sizeof(result) * 8; // Shift requested bits from the 9th byte into the right position to be combined with result. ninth_byte <<= (bit_size - offset_start); // merge value together from all nine bytes. result = result | ninth_byte; } // BE Signal needs byte order swapping. if (endianess == bit_big_endian) { // Only for reading partial bytes. Filling the missing bits differs from LE Signal. if (bit_length % 8 != 0) { /* Shift left to align end position. * * 000|0abcdefg|hijklmno| => 000|abcdefgh|ijklmno0| * */ result <<= (offset_end + offset_start) % 8; /* Shift bits within MSByte, filling the gap with 0s. * * 000|abcdefgh|ijklmno0| => 000|abcdefgh|0ijklmno| * ^ ^ * MSByte MSByte */ uint8_t *ms_byte = (uint8_t*)&result; ms_byte[0] >>= (offset_end + offset_start) % 8; } /* swap bytes to LE. * * 000|abcdefgh|0ijklmno| => 000|0ijklmno|abcdefgh| * */ detail::convertSignalEndianess(&result, endianess, bit_length); } } // On BE System else { /* swap bytes to simulate LE shifting operations. * * |...abcde|fghijklm|no......|... => ...|no......|fghijklm|...abcde| * */ detail::convertSignalEndianess(&result, bit_little_endian, sizeof(result) * 8); // LE Signal if (endianess == bit_little_endian) { /* Use bit mask to remove bits on the higher end, which do not belong to the value to read. * * ...|no......|fghijklm|...abcde| => ...|no......|fghijklm|000abcde| * */ cutLeadingBits(&result, (sizeof(result) * 8) - offset_end); // Cut away only offset_end bits. /* Shift right to align bits within LSByte (BE Shifting!). * * ...|no......|fghijklm|000abcde| => ...|hijklmno|0abcdefg|00000000| * */ result >>= offset_start; /* Shift right to align LSByte on the left side (BE Shifting!). * * ...|hijklmno|0abcdefg|00000000| => |hijklmno|0abcdefg|000 * * Shift over all * empty bytes = (all bits - occupied bits (bit_length) - already shifted bits) / number of bytes */ result >>= (((sizeof(result) * 8) - bit_length - offset_start) / sizeof(result)) * 8; // No further byte swap, because there has been one swap before the shift operations already. } // BE Signal else { /* Shift left to align bits within LSByte (now rightmost byte because of byte swap). * Also deletes bits from end offset. * * ...|no......|fghijklm|...abcde| => ...|........|ijklmno.|abcdefgh| * */ result <<= offset_end; // Only for reading partial bytes. Filling the missing bits differs from LE Signal. if (bit_length % 8 != 0) { /* Shift bits within MSByte to move 0s to the highest bits (also deleting all unwanted bits from start offset). * * ...|........|ijklmno.|abcdefgh| => ...|........|0ijklmno|abcdefgh| * */ uint8_t *ms_byte = (uint8_t*)&result + (bit_length / 8); // position of the value's MSByte ms_byte[0] >>= (offset_end + offset_start) % 8; // amount of unused bits within MSByte } /* swap bytes back to BE * * ...|........|0ijklmno|abcdefgh| => |abcdefgh|0ijklmno|... * */ detail::convertSignalEndianess(&result, bit_little_endian, sizeof(result) * 8); // Change the simulated LE value back /* Use bit mask to remove bits on the higher end, which do not belong to the value to read. * * |abcdefgh|0ijklmno|... => |abcdefgh|0ijklmno|000 * * Remove everything behind the value length plus the gap within MSByte. */ cutLeadingBits(&result, bit_length + (offset_end + offset_start) % 8); } } // Copy the resulting value to the target variable. No Casting! Data might be lost otherwise. size_t sz = std::min(sizeof(*value), sizeof(result)); a_util::memory::copy(value, sz, &result, sz); return a_util::result::SUCCESS; } /** * Write value to bitfield. * Operating on a uint64_t copy to allow bit shifting and masking operations. * * @param [in] buffer Pointer to the memory buffer to write to. * @param [in] start_bit Bit position to start writing to. The least significant bit * has the index 0. * @param [in] bit_length Number of bits to write. * @param [out] value Value to write to the bitfield. * @param [in] endianess Parameter describing the endianess of the bitfield to write to. * * @return Returns a standard result code. */ static a_util::result::Result writeSignal(uint8_t *buffer, size_t start_bit, size_t bit_length, T value, Endianess endianess = get_platform_endianess()) { // 1) Copy relevant bytes of Buffer content to be overwritten. uint64_t buffer_copy = 0; uint64_t ninth_byte = 0; // storage variable for the ninth bit from buffer size_t bytes_to_read = 0; copyBytesFromBuffer(buffer, &buffer_copy, start_bit, bit_length, &ninth_byte, &bytes_to_read); // 2) Erase Bits from Buffer copy that will be overwritten TODO: BE LE system difference important here? // Number of bits the start position is offset from 0 size_t offset_start = start_bit % 8; // Number of bits the end position is offset from the end of the last byte size_t offset_end = (8 - ((start_bit + bit_length) % 8)) % 8; uint64_t mask_left = ~0ULL; if ((bit_length + offset_start) >= (sizeof(mask_left) * 8)) { mask_left = 0; } else { mask_left = ~0ULL; mask_left <<= (bit_length + offset_start); } uint64_t mask = ~0ULL; mask <<= offset_start; mask = ~mask; mask |= mask_left; buffer_copy &= mask; // 3) Copy value to UInt64 variable to work with. uint64_t signal; a_util::memory::copy(&signal, sizeof(signal), &value, sizeof(signal)); // 4) Keep only nLength bits: Remove bits that should not be written to the Buffer. cutLeadingBits(&signal, bit_length); // 5) Shift to align at start bit position. int shift_amount = (int)offset_start; // Initialized to fit LE Signal shift // BE Signal if (endianess == bit_big_endian) { // swap bytes detail::convertSignalEndianess(&signal, endianess, bit_length); // Remove gap for partial bytes within MSByte uint8_t *ms_byte = (uint8_t*)&signal; int ms_shift = (8 - (bit_length % 8)) % 8; ms_byte[0] <<= ms_shift; shift_amount -= ms_shift; } // Copy most significant byte to ninth buffer byte before losing bits with the shift. if ((offset_start + bit_length) > (sizeof(signal) * 8)) { uint64_t signal_for_ninth_byte = signal; signal_for_ninth_byte >>= (sizeof(signal) - 1) * 8; signal_for_ninth_byte >>= (8 - offset_start); // Only LE Signal uint64_t mask = ~0ULL; mask <<= (8 - offset_end); ninth_byte &= mask; ninth_byte |= signal_for_ninth_byte; } if (shift_amount < 0) { signal >>= std::abs(shift_amount); } else { signal <<= shift_amount; } // 7) Write bytes with integrated signal back to the buffer. buffer_copy |= signal; size_t sz = std::min(bytes_to_read, sizeof(signal)); a_util::memory::copy(buffer + (start_bit / 8), sz, &buffer_copy, sz); // Eventually copy ninth byte back to buffer if (bytes_to_read > sizeof(signal)) { a_util::memory::copy(buffer + (start_bit / 8) + sizeof(signal), 1, &ninth_byte, 1); } return a_util::result::SUCCESS; } /** * Set the highest bits of a uint64_t value to zero. The number of bit_length lowest bits * remain unchanged. * * @param [out] value Pointer to the variable to trim. * @param [in] bit_length Number of trailing bits to remain unchanged. * * @return Returns a standard result code. */ static a_util::result::Result cutLeadingBits(uint64_t *value, size_t bit_length) { size_t bit_size = (sizeof(*value) * 8); if (bit_length < bit_size) { uint64_t mask = ~0ULL; mask >>= (bit_size - bit_length); *value &= mask; } return a_util::result::SUCCESS; } /** * Copy bytes_to_read number of bytes from the buffer to value and ninth_byte. * Determines how many bytes need to be copied to receive a copy of all bits in the range described * by start_bit and bit_length. The maximum for bit_length is 64, but for unaligned values the range * may exceed 8 bytes. In this case, the required ninth byte will be copied to ninth_byte. * * @param [in] buffer Pointer to the memory buffer to copy from. * @param [out] value Pointer to the variable to store the copied value in. * @param [in] start_bit Bit position to start reading from. The least significant bit * has the index 0. * @param [in] bit_length Number of bits to read. * @param [out] ninth_byte Pointer to the variable to eventually store a copied ninth byte in. * @param [out] bytes_to_read Number of bytes that need to be copied to attain all requested bits. * * @return Returns a standard result code. */ static a_util::result::Result copyBytesFromBuffer(uint8_t *buffer, uint64_t *value, size_t start_bit, size_t bit_length, uint64_t *ninth_byte, size_t *bytes_to_read) { // Byte within the buffer to start reading at size_t start_byte = start_bit / 8; // Number of bits to read: signal length + bits to fill in the offset on both sides for unaligned signals size_t bits_to_read = bit_length + (start_bit % 8); if ((bits_to_read % 8) > 0) { bits_to_read += (8 - (bits_to_read % 8)); } // Number of bytes to read from the buffer *bytes_to_read = bits_to_read / 8; // Copy up to 8 bytes to result if (*bytes_to_read > (size_t)sizeof(*value)) { a_util::memory::copy(value, sizeof(*value), buffer + start_byte, sizeof(*value)); } else { a_util::memory::copy(value, *bytes_to_read, buffer + start_byte, *bytes_to_read); } // The max signal size is 8 byte, but if the signal is not aligned, it might spread over 9 bytes. if (*bytes_to_read > sizeof(*value)) { // Get a copy of the most significant byte, which could not yet be saved to result a_util::memory::copy(ninth_byte, 1, buffer + start_byte + sizeof(*value), 1); } return a_util::result::SUCCESS; } }; /// Template converter class to differentiate between float, signed and unsigned integer values. template class Converter; /// Partially specialized class for Unsigned Integers template class Converter : public ConverterBase { public: /** * Read unsigned integer from bitfield. * * @param [in] buffer Pointer to the memory buffer to read from. * @param [in] start_bit Bit position to start reading from. The least significant bit * has the index 0. * @param [in] bit_length Number of bits to read. * @param [out] value Pointer to the variable to store the read value in. * @param [in] endianess Parameter describing the endianess of the bitfield to read from. * * @return Returns a standard result code. */ static a_util::result::Result read(uint8_t *buffer, size_t start_bit, size_t bit_length, T* value, Endianess endianess) { return ConverterBase::readSignal(buffer, start_bit, bit_length, value, endianess); } /** * Write unsigned integer to bitfield. * * @param [in] buffer Pointer to the memory buffer to write to. * @param [in] start_bit Bit position to start writing to. The least significant bit * has the index 0. * @param [in] bit_length Number of bits to write. * @param [out] value Value to write to the bitfield. * @param [in] endianess Parameter describing the endianess of the bitfield to write to. * * @return Returns a standard result code. */ static a_util::result::Result write(uint8_t *buffer, size_t start_bit, size_t bit_length, T value, Endianess endianess) { return ConverterBase::writeSignal(buffer, start_bit, bit_length, value, endianess); } }; /// Partially specialized class for Signed Integers template class Converter : public ConverterBase { public: /** * Read signed integer from bitfield. * * @param [in] buffer Pointer to the memory buffer to read from. * @param [in] start_bit Bit position to start reading from. The least significant bit * has the index 0. * @param [in] bit_length Number of bits to read. * @param [out] value Pointer to the variable to store the read value in. * @param [in] endianess Parameter describing the endianess of the bitfield to read from. * * @return Returns a standard result code. */ static a_util::result::Result read(uint8_t *buffer, size_t start_bit, size_t bit_length, T* value, Endianess endianess) { a_util::result::Result res = ConverterBase::readSignal(buffer, start_bit, bit_length, value, endianess); if (res != a_util::result::SUCCESS) { return res; } // replicate sign bit *value <<= (sizeof(T) * 8) - bit_length; *value >>= (sizeof(T) * 8) - bit_length; return a_util::result::SUCCESS; } /** * Write signed integer to bitfield. * * @param [in] buffer Pointer to the memory buffer to write to. * @param [in] start_bit Bit position to start writing to. The least significant bit * has the index 0. * @param [in] bit_length Number of bits to write. * @param [out] value Value to write to the bitfield. * @param [in] endianess Parameter describing the endianess of the bitfield to write to. * * @return Returns a standard result code. */ static a_util::result::Result write(uint8_t *buffer, size_t start_bit, size_t bit_length, T value, Endianess endianess) { // Nothing special to take care of for writing signed integers, compared to writing unsigned integers. return ConverterBase::writeSignal(buffer, start_bit, bit_length, value, endianess); } }; /// Specialized class for Floats (Floats are always signed!) template class Converter : public ConverterBase { public: /** * Read tFloat from bitfield. * * @param [in] buffer Pointer to the memory buffer to read from. * @param [in] start_bit Bit position to start reading from. The least significant bit * has the index 0. * @param [in] bit_length Number of bits to read. * @param [out] value Pointer to the variable to store the read value in. * @param [in] endianess Parameter describing the endianess of the bitfield to read from. * * @return Returns a standard result code. */ static a_util::result::Result read(uint8_t *buffer, size_t start_bit, size_t bit_length, T* value, Endianess endianess) { // Read only values of size tFloat if (sizeof(T) * 8 == bit_length) { return ConverterBase::readSignal(buffer, start_bit, bit_length, value, endianess); } else { return ERR_INVALID_ARG; } } /** * Write tFloat to bitfield. * * @param [in] buffer Pointer to the memory buffer to write to. * @param [in] start_bit Bit position to start writing to. The least significant bit * has the index 0. * @param [in] bit_length Number of bits to write. * @param [out] value Value to write to the bitfield. * @param [in] endianess Parameter describing the endianess of the bitfield to write to. * * @return Returns a standard result code. */ static a_util::result::Result write(uint8_t *buffer, size_t start_bit, size_t bit_length, T value, Endianess endianess) { // Write only values of size tFloat if (sizeof(T) * 8 == bit_length) { return ConverterBase::writeSignal(buffer, start_bit, bit_length, value, endianess); } else { return ERR_INVALID_ARG; } } }; } // namespace detail /// Bit Serializer Class class BitSerializer { public: /** * Constructor */ BitSerializer(void* data, size_t data_size) : _buffer(static_cast(data)), _buffer_bytes(data_size), _buffer_bits(_buffer_bytes * 8) { } /** * Default Constructor */ BitSerializer() : _buffer(NULL), _buffer_bytes(0), _buffer_bits(0) { } /** * Read value from bitfield. Value can be of type tFloat or an unsigned or signed integer. * * ....|...*****|********|**......|.... Buffer (index 0 on the right end side) * |_______________| * bit_length ^ * | * start_bit * * @param [in] start_bit Bit position to start reading from. The least significant bit * has the index 0. * @param [in] bit_length Number of bits to read. * @param [out] value Pointer to the variable to store the read value in. * @param [in] endianess Parameter describing the endianess of the bitfield to read from. * * @return Returns a standard result code. */ template a_util::result::Result read(size_t start_bit, size_t bit_length, T* value, Endianess endianess = get_platform_endianess()) { // Check if in range a_util::result::Result result_code = checkForInvalidArguments(start_bit, bit_length, sizeof(T)); if (result_code != a_util::result::SUCCESS) { return result_code; } // Call template function detail::Converter::value, std::is_floating_point::value> ::read(_buffer, start_bit, bit_length, value, endianess); return a_util::result::SUCCESS; } /** * Write value to bitfield. Value can be of type tFloat or an unsigned or signed integer. * * ....|...*****|********|**......|.... Buffer (index 0 on the right end side) * |_______________| * bit_length ^ * | * start_bit * * @param [in] start_bit Bit position to start writing to. The least significant bit * has the index 0. * @param [in] bit_length Number of bits to write. * @param [out] value Value to write to the bitfield. * @param [in] endianess Parameter describing the endianess of the bitfield to write to. * * @return Returns a standard result code. */ template a_util::result::Result write(size_t start_bit, size_t bit_length, T value, Endianess endianess = get_platform_endianess()) { // Check if in range a_util::result::Result result_code = checkForInvalidArguments(start_bit, bit_length, sizeof(T)); if (result_code != a_util::result::SUCCESS) { return result_code; } // Call template function detail::Converter::value, std::is_floating_point::value> ::write(_buffer, start_bit, bit_length, value, endianess); return a_util::result::SUCCESS; } private: /// internal buffer uint8_t *_buffer; /// size of internal buffer in bytes size_t _buffer_bytes; /// size of internal buffer in bits size_t _buffer_bits; /** * Check if the parameters for the reading and writing access are valid. * The variable to read from or into might be too small and the accessed region of the memory buffer * might be out of range. * * @param [in] start_bit Bit position to start reading from. The least significant bit * has the index 0. * @param [in] bit_length Number of bits to read. * @param [in] size_variable Size of the variable to read into or write from. * * @return Returns a standard result code. */ a_util::result::Result checkForInvalidArguments(size_t start_bit, size_t bit_length, size_t size_variable) { if (!_buffer) { return ERR_POINTER; } // Check invalid starting point if (start_bit >= _buffer_bits) { return ERR_INVALID_ARG; } // Check out of buffer bounds or length < 1 if ((bit_length < 1) || (_buffer_bits < start_bit + bit_length)) { return ERR_INVALID_ARG; } // Check variable size if (size_variable * 8 < bit_length) { return ERR_INVALID_ARG; } return a_util::result::SUCCESS; } }; } // namespace memory } // namespace a_util #endif // A_UTILS_UTIL_MEMORY_BITSERIALIZER_INCLUDED