/* * PacketLib.h * * Created on: Nov 24, 2022 * Author: valerio */ #pragma once #include #include #include #include #include #include #include #include #include #include namespace inaf::oasbo::PacketLib { class BasePacketStructure { protected: std::string source; std::vector> structure; uint byteSize; std::vector fieldSizes; std::unordered_map indexToOffsetsMap; std::unordered_map fieldNameToIndexMap; std::unordered_map indexToFieldNameMap; virtual std::vector> readStructureFromSource( std::string source) = 0; void updateFieldSizes( const std::vector> ¶msTuple) { std::for_each(paramsTuple.begin(), paramsTuple.end(), [&](const std::tuple &tup) { this->fieldSizes.push_back(std::get<2>(tup)); }); } void updateFieldOffsets( const std::vector> ¶msTuple) { uint offset = 0; for (size_t i = 0; i < paramsTuple.size(); i++) { indexToOffsetsMap[i] = offset; offset += std::get<2>(paramsTuple[i]); } } void updateFieldNameAndIndexMap( const std::vector> ¶msTuple) { std::for_each(paramsTuple.begin(), paramsTuple.end(), [&](const std::tuple &tup) { this->fieldNameToIndexMap[std::get<1>(tup)] = std::get<0>( tup); this->indexToFieldNameMap[std::get<0>(tup)] = std::get<1>( tup); }); } void updateStructure(std::string source) { this->source = source; this->structure = readStructureFromSource(source); updateFieldSizes(structure); updateFieldOffsets(structure); updateFieldNameAndIndexMap(structure); this->byteSize = std::accumulate(fieldSizes.begin(), fieldSizes.end(),0) / 8; // /8 for byte size this->byteSize += this->byteSize%8 == 0 ? 0 : 1; // add another byte to contain extra bits } public: virtual ~BasePacketStructure() = default; BasePacketStructure(std::string source) : source(source){}; BasePacketStructure(const BasePacketStructure &other){ this->source = other.source; this->byteSize = other.byteSize; this->fieldSizes = other.fieldSizes; this->indexToOffsetsMap = other.indexToOffsetsMap; this->fieldNameToIndexMap = other.fieldNameToIndexMap; this->indexToFieldNameMap = other.indexToFieldNameMap; } void changeSource(std::string source) { updateStructure(source); } uint getByteSize() { return this->byteSize; } std::string getSource() { return this->source; } std::optional bitOffsetOf(uint index) { if (index <= this->numberOfFields()) { return indexToOffsetsMap.at(index); } else { std::cerr << "No field at " << index << ", max is " << numberOfFields() << ", returning nullopt." << std::endl; return std::nullopt; } } std::optional bitSizeOf(uint index) { if (index <= this->numberOfFields()) { return this->fieldSizes[index]; } else { std::cerr << "No field at " << index << ", max is " << numberOfFields() << ", returning nullopt." << std::endl; return std::nullopt; } } std::optional indexOfField(std::string fieldName) { try { return this->fieldNameToIndexMap.at(fieldName); } catch (const std::out_of_range &oor) { std::cerr << "No field of name " << fieldName << ", returning nullopt." << std::endl; return std::nullopt; } } std::optional fieldNameOfIndex(uint index) { try { return this->indexToFieldNameMap.at(index); } catch (const std::out_of_range &oor) { std::cerr << "No field at " << index << ", max is " << numberOfFields() << ", returning nullopt." << std::endl; return std::nullopt; } } uint numberOfFields() { return this->fieldSizes.size(); } std::unordered_map getFieldNameToIndexMap() const { return this->fieldNameToIndexMap; } }; template class bit_iterator: public std::iterator { public: bit_iterator(const uint8_t *data, int offset, BasePacketStructure *structure, std::function func) : m_data(data), m_offset(offset), m_structure(structure), m_func(func) { } bit_iterator(const bit_iterator &other) : m_data(other.m_data), m_offset(other.m_offset), m_structure( other.m_structure), m_func(other.m_func) { } bit_iterator& operator++() { ++m_offset; return *this; } bit_iterator operator++(int) { bit_iterator temp(*this); ++m_offset; return temp; } bit_iterator& operator--() { --m_offset; return *this; } bit_iterator operator--(int) { bit_iterator temp(*this); --m_offset; return temp; } bit_iterator operator+(int n) const { return bit_iterator(m_data, m_offset + n, m_structure, m_func); } bit_iterator operator-(int n) const { return bit_iterator(m_data, m_offset - n, m_structure, m_func); } int operator-(const bit_iterator &other) const { return m_offset - other.m_offset; } bool operator==(const bit_iterator &other) const { return m_data == other.m_data && m_offset == other.m_offset; } bool operator!=(const bit_iterator &other) const { return !(*this == other); } ValueType operator*() const { auto offset = m_structure->bitOffsetOf(m_offset); // offset from the beginning of the byte auto num_bits = m_structure->bitSizeOf(m_offset); return m_func(m_data, offset.value(), num_bits.value()); } private: const uint8_t *m_data; int m_offset; BasePacketStructure *m_structure; std::function m_func; }; template class BasePacketTempl { protected: BasePacketStructure *structure; uint8_t *binaryPointer; // This C++ function reads a binary value from a memory location pointed to by binaryPointer. // The binary value is represented by num_bits number of bits starting from the offset-th bit in the memory. // The function returns the value of the binary as a ValueType. static ValueType _readValueFromBinaryAt_(const uint8_t *binaryPointer, uint offset, uint num_bits) { // Calculate the bit offset from the byte offset: auto bit_offset = offset % 8; // Calculate the byte offset from the bit offset: auto byte_offset = offset / 8; ValueType value = 0; for (uint i = 0; i < num_bits; i++) { // Calculate the byte and bit index of the current bit: auto byte_index = byte_offset + (bit_offset + i) / 8; auto bit_index = (bit_offset + i) % 8; uint8_t byte = binaryPointer[byte_index]; // Create a bit mask to isolate the desired bit: uint8_t bit_mask = 1 << (7 - bit_index); // Set the corresponding bit in the return value if the retrieved bit is 1: value |= (byte & bit_mask) ? (1 << (num_bits - i - 1)) : 0; } return value; } uint minBitsRequired(size_t value) const { // Handle special cases if (value == 0) { return 1; } else if (value == (size_t) -1) { return sizeof(value) * 8; } // Calculate the number of bits needed auto bitsNeeded = 0; bool isNegative = value < 0; uint32_t absValue = isNegative ? -value : value; while (absValue != 0) { bitsNeeded++; absValue >>= 1; } if (isNegative) { bitsNeeded++; } return bitsNeeded; } public: BasePacketTempl(BasePacketStructure &structure) { this->structure = &structure; this->binaryPointer = new uint8_t[structure.getByteSize()]; } BasePacketTempl(const BasePacketTempl& other) { this->structure = new BasePacketStructure(other.getPacketStructure()); this->binaryPointer = new uint8_t[other.structure->getByteSize()]; std::memcpy(this->binaryPointer, other.binaryPointer, other.structure->getByteSize()); } virtual ~BasePacketTempl() = default; void updateStructure(BasePacketStructure &structure) { size_t newSize = std::min(structure.getByteSize(), this->structure->getByteSize()); this->structure = &structure; uint8_t *buff = new uint8_t[newSize]; std::memcpy(buff, binaryPointer, newSize); delete this->binaryPointer; this->binaryPointer = new uint8_t[newSize]; std::memcpy(binaryPointer, buff, newSize); delete buff; } std::optional readValueFromBinaryAt(uint index) const { auto offset = structure->bitOffsetOf(index); // offset from the beginning of the byte auto num_bits = structure->bitSizeOf(index); //remaining bits to read if (offset.has_value() && num_bits.has_value()) return _readValueFromBinaryAt_(binaryPointer, offset.value(), num_bits.value()); else { std::cerr << "Error: No field at " << index << ", max is " << structure->numberOfFields() << ", returning nullopt" << std::endl; return std::nullopt; } } uint8_t const* getBinaryPointer() const { return binaryPointer; } int copyToBinaryPointer(const uint8_t *from, uint size) { uint max_writable = this->structure->getByteSize(); if (size > max_writable) { std::cerr << "Error: you are trying to copy " << size << "byte where the max size is: " << max_writable << std::endl; std::cerr << "\tI copy only until " << max_writable << " byte" << std::endl; std::memcpy(binaryPointer, from, max_writable); return max_writable; } else { std::memcpy(binaryPointer, from, size); return size; } } int copyToBinaryPointer(const uint8_t *from, uint size, uint offset) { uint max_writable = this->structure->getByteSize(); if (offset > max_writable) { std::cerr << "Error: you are trying to copy starting from " << offset << "byte where the max size is: " << max_writable << std::endl; return -1; } if (size + offset > max_writable) { std::cerr << "Error: you are trying to copy " << size + offset << "byte where the max size is: " << max_writable << std::endl; std::cerr << "\tI copy only until " << max_writable << " byte" << std::endl; int to_write = max_writable - offset; std::memcpy(&binaryPointer[offset], from, to_write); return to_write; } else { std::memcpy(&binaryPointer[offset], from, size); return size; } } std::optional operator[](uint index) const { return readValueFromBinaryAt(index); } std::optional operator[](std::string fieldName) const { auto index = structure->indexOfField(fieldName); if (index.has_value()) return readValueFromBinaryAt(index.value()); else return std::nullopt; } bit_iterator begin() const { return bit_iterator(binaryPointer, 0, structure, &_readValueFromBinaryAt_); } bit_iterator end() const { return bit_iterator(binaryPointer, structure->numberOfFields(), structure, &_readValueFromBinaryAt_); } std::optional writeValueToBinaryAtIndex(uint index, ValueType value) { auto offset = this->structure->bitOffsetOf(index); auto numbits = this->structure->bitSizeOf(index); size_t min_req = minBitsRequired(value); if (!offset.has_value() || !numbits.has_value()) return std::nullopt; if (numbits < min_req) { std::cerr << "Error: you are trying to write " << value << " which requires at least " << min_req << " bits in a field of size " << numbits << std::endl; return std::nullopt; } // Calculate the bit offset from the byte offset: int bitoffset = offset.value() % 8; // Calculate the byte offset from the bit offset: int byteoffset = offset.value() / 8; int numbits_written = 0; for (size_t i = 0; i < numbits; i++) { // Calculate the byte and bit index of the current bit: int byte_index = byteoffset + (bitoffset + i) / 8; int bit_index = (bitoffset + i) % 8; // Create a bit mask to isolate the desired bit: uint8_t bit_mask = 1 << (7 - bit_index); // Set the corresponding bit in the binary array if the value is 1: if ((value >> (numbits.value() - i - 1)) & 1) { binaryPointer[byte_index] |= bit_mask; numbits_written++; } else { binaryPointer[byte_index] &= ~bit_mask; } } return numbits_written; } uint getPacketStructureByteSize() const { return structure->getByteSize(); } BasePacketStructure& getPacketStructure() const { return *(this->structure); } virtual bool isRecognizedHeader() const = 0; virtual bool isRecognizedHeader(std::vector buff) const = 0; virtual uint getHeaderSize() const = 0; virtual uint getPayloadSize() const = 0; virtual uint getTailSize() const = 0; }; using valueType = size_t; class BasePacket: public BasePacketTempl { protected: public: BasePacket(BasePacketStructure &structure) : BasePacketTempl(structure) { } virtual ~BasePacket() = default; }; }