#pragma once #include #include #include #include #include #include #include #include #include #include #include #include namespace inaf::oasbo::Packets { /** * @class BasePacketStructure * @brief Represents the structure of a base packet. * * The BasePacketStructure class provides functionality to define and manipulate the structure of a base packet. * It stores information about the fields, their sizes, offsets, and mappings between field names and indices. * The class also provides methods to retrieve information about the structure, such as the byte size, field offsets, and field names. * * @note This is an abstract base class and should be derived to define specific packet structures. In particular, you should define * the sourceReadingFunc function. */ class BasePacketStructure { protected: /** * @brief Type alias for the packet structure. * * This alias represents the structure of a packet. It is defined as a vector of tuples, where each tuple contains three elements: * - The first element is of type uint and represents the field index. * - The second element is of type std::string and represents the field name. * - The third element is of type uint and represents the field bit size. */ using Structure = std::vector>; /**< Type alias for the packet structure. */ std::string source; /**< The source of the packet. */ Structure structure; /**< The structure of the packet. */ uint byteSize; /**< The size of the packet in bytes. */ std::vector fieldSizes; /**< vector of the sizes of the fields in the packet. */ std::unordered_map indexToOffsetsMap; /**< Mapping of field index to field offset. */ std::unordered_map fieldNameToIndexMap; /**< Mapping of field name to field index. */ std::unordered_map indexToFieldNameMap; /**< Mapping of field index to field name. */ std::function sourceReadingFunc; /**< Function to read the packet source and return the structure. */ /** * @brief Updates the field sizes based on the structure. * @param structure The structure of the packet. */ void updateFieldSizes(const Structure &structure) { std::for_each(structure.begin(), structure.end(), [&](const std::tuple &tup) { this->fieldSizes.push_back(std::get<2>(tup)); }); } /** * @brief Updates the field offsets based on the structure. * @param structure The structure of the packet. */ void updateFieldOffsets(const Structure &structure) { uint offset = 0; for (size_t i = 0; i < structure.size(); i++) { indexToOffsetsMap[i] = offset; offset += std::get<2>(structure[i]); } } /** * @brief Updates the field name and index maps based on the structure. * @param structure The structure of the packet. */ void updateFieldNameAndIndexMap(const Structure &structure) { std::for_each(structure.begin(), structure.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); }); } /** * @brief Updates the packet structure based on the source. * @param source The source of the packet structure. */ void updateStructure(std::string source) { this->source = source; this->structure = sourceReadingFunc(source); updateFieldSizes(structure); updateFieldOffsets(structure); updateFieldNameAndIndexMap(structure); uint bitSize = std::accumulate(fieldSizes.begin(), fieldSizes.end(), 0); this->byteSize = bitSize % 8 == 0 ? bitSize / 8 : bitSize / 8 + 1; } public: /** * @brief Destructor. */ virtual ~BasePacketStructure() = default; /** * @brief Constructor. * @param source The source of the packet structure. * @param sourceReadingFunc The function to read the packet source and return the structure. */ BasePacketStructure(std::string source, std::function sourceReadingFunc) : source(source), sourceReadingFunc(sourceReadingFunc) { this->updateStructure(source); } /** * @brief Copy constructor. * @param other The other BasePacketStructure object to copy from. */ 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; this->sourceReadingFunc = other.sourceReadingFunc; } /** * @brief Changes the source of the packet and updates the structure. * @param source The new source of the packet structure. */ void changeSource(std::string source) { updateStructure(source); } /** * @brief Gets the size of the packet in bytes. * @return The size of the packet in bytes. */ uint getByteSize() { return this->byteSize; } /** * @brief Gets the source of the packet structure. * @return The source of the packet structure. */ std::string getSource() { return this->source; } /** * @brief Gets the bit offset of a field in the packet structure. * @param index The index of the field. * @return The bit offset of the field, or std::nullopt if the field does not exist. */ std::optional bitOffsetOf(uint index) { if (index <= this->numberOfFields()) { return indexToOffsetsMap.at(index); } else { time_t now = time(nullptr); std::cerr << "[" << std::put_time(localtime(&now), "%Y-%m-%d %H:%M:%S") << "]\t[Base Packet]\t" << "No field at " << index << ", max is " << numberOfFields() << ", returning nullopt." << std::endl; return std::nullopt; } } /** * @brief Gets the bit size of a field in the packet structure. * @param index The index of the field. * @return The bit size of the field, or std::nullopt if the field does not exist. */ std::optional bitSizeOf(uint index) { if (index <= this->numberOfFields()) { return this->fieldSizes[index]; } else { time_t now = time(nullptr); std::cerr << "[" << std::put_time(localtime(&now), "%Y-%m-%d %H:%M:%S") << "]\t[Base Packet]\t" << "No field at " << index << ", max is " << numberOfFields() << ", returning nullopt." << std::endl; return std::nullopt; } } /** * @brief Gets the index of a field in the packet structure based on its name. * @param fieldName The name of the field. * @return The index of the field, or std::nullopt if the field does not exist. */ std::optional indexOfField(std::string fieldName) { std::transform(fieldName.begin(), fieldName.end(), fieldName.begin(), [](unsigned char c) { return std::tolower(c); }); try { return this->fieldNameToIndexMap.at(fieldName); } catch (const std::out_of_range &oor) { time_t now = time(nullptr); std::cerr << "[" << std::put_time(localtime(&now), "%Y-%m-%d %H:%M:%S") << "]\t[Base Packet]\t" << "No field of name " << fieldName << ", returning nullopt." << std::endl; return std::nullopt; } } /** * @brief Gets the name of a field in the packet based on its index. * @param index The index of the field. * @return The name of the field, or std::nullopt if the field does not exist. */ std::optional fieldNameOfIndex(uint index) { try { return this->indexToFieldNameMap.at(index); } catch (const std::out_of_range &oor) { time_t now = time(nullptr); std::cerr << "[" << std::put_time(localtime(&now), "%Y-%m-%d %H:%M:%S") << "]\t[Base Packet]\t" << "No field at " << index << ", max is " << numberOfFields() << ", returning nullopt." << std::endl; return std::nullopt; } } /** * @brief Gets the number of fields in the packet. * @return The number of fields in the packet. */ uint numberOfFields() { return this->fieldSizes.size(); } /** * @brief Gets the field name to index map. * @return The field name to index map. */ std::unordered_map getFieldNameToIndexMap() const { return this->fieldNameToIndexMap; } /** * @brief Gets the index to offset map. * @return The index to offset map. */ std::unordered_map getIndexToOffsetsMap() const { return this->indexToOffsetsMap; } /** * @brief Gets the index to field name map. * @return The index to field name map. */ std::unordered_map getIndexToFieldNameMap() const { return this->indexToFieldNameMap; } }; /** * @brief Represents an iterator that iterates over individual fields in the packet. * * The `bit_iterator` class provides a random access iterator interface for iterating over individual fields in the packet. * It is templated on the value type of the packet fields. I.e. if the largest packet fields is 32-bit, then the value type should be at least uint32_t. * * @tparam ValueType The value type of the values returned by the iterator. The value */ template class bit_iterator { using iterator_category = std::random_access_iterator_tag; using value_type = ValueType; using difference_type = std::ptrdiff_t; using pointer = ValueType *; using reference = ValueType &; private: /** * @brief Pointer to the packet data. */ const uint8_t *m_data; /** * @brief Current offset within the packet. */ int m_offset; /** * @brief Pointer to the BasePacketStructure. */ BasePacketStructure *m_structure; /** * @brief Function to retrieve the value from data based on offset and size. */ std::function m_func; 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()); } }; /** * @brief Represents a template class for handling packets with a specific structure. * * The `BasePacketTempl` class provides a generic template for handling packets with a specified structure. * It includes methods for reading and writing values from/to memory, as well as other utility functions. * * @tparam ValueType The value type of the largest packet fields. i.e The value type should be at least uint32_t if the largest packet field is 32-bit. */ template class BasePacketTempl { protected: BasePacketStructure *packetStructure; /**< Pointer to the structure defining the packet. */ uint8_t *memoryPointer; /**< Pointer to the memory containing packet data. */ /** * @brief reads a binary value from a memory location pointed to by binaryPointer. * The binary value is represented by num_bits bits starting from the offset-th bit in the memory. * * @param binaryPointer Pointer to the memory containing the binary value. * @param offset The offset of the first bit of the binary value. * @param num_bits The number of bits to read. * @return ValueType The value read from memory. */ static ValueType _readValueFromMemoryAt_(const uint8_t *binaryPointer, uint offset, uint num_bits) { // Calculate the bit offset from the byte offset: uint bit_offset = offset % 8; // Calculate the byte offset from the bit offset: uint byte_offset = offset / 8; ValueType value = 0; ValueType bit = 1; for (uint i = 0; i < num_bits; i++) { // Calculate the byte and bit index of the current bit: uint byte_index = byte_offset + (bit_offset + i) / 8; uint 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) ? (bit << (num_bits - i - 1)) : 0; } return value; } /** * @brief Calculates the minimum number of bits required to represent a given value. * * @param value The value to calculate the minimum bits for. * @return size_t The minimum number of bits required. */ size_t minBitsRequired(size_t value) const { // Handle special cases size_t bitsNeeded = 1; while (value != 0) { bitsNeeded++; value >>= 1; } return bitsNeeded; } public: /** * @brief Constructor for the BasePacketTempl class. * * @param structure The structure defining the packet. */ BasePacketTempl(BasePacketStructure &structure) { this->packetStructure = new BasePacketStructure(structure); this->memoryPointer = new uint8_t[structure.getByteSize()]; } /** * @brief Copy constructor for the BasePacketTempl class. * * @param other The other BasePacketTempl object to copy. */ BasePacketTempl(const BasePacketTempl &other) { this->packetStructure = new BasePacketStructure( other.getPacketStructure()); this->memoryPointer = new uint8_t[other.packetStructure->getByteSize()]; std::memcpy(this->memoryPointer, other.memoryPointer, other.packetStructure->getByteSize()); } /** * @brief Destructor for the BasePacketTempl class. */ virtual ~BasePacketTempl() = default; /** * @brief Updates the packet structure. * @param structure The new packet structure. */ void updatePacketStructure(BasePacketStructure &structure) { size_t newSize = structure.getByteSize(); size_t oldSize = this->packetStructure->getByteSize(); uint8_t *buff = new uint8_t[newSize]; std::memset(buff, 0, newSize); std::memcpy(buff, memoryPointer, std::min(newSize, oldSize)); delete this->memoryPointer; this->memoryPointer = new uint8_t[newSize]; std::memcpy(memoryPointer, buff, newSize); delete[] buff; this->packetStructure = &structure; } /** * @brief Reads a value from the memory at a given index. * @param index The index of the field to read. * @return std::optional The value read from memory, or std::nullopt if the field does not exist. */ std::optional readValueFromMemoryAt(uint index) const { auto offset = packetStructure->bitOffsetOf(index); // offset from the beginning of the byte auto num_bits = packetStructure->bitSizeOf(index); // remaining bits to read if (offset.has_value() && num_bits.has_value()) return _readValueFromMemoryAt_(memoryPointer, offset.value(), num_bits.value()); else { time_t now = time(nullptr); std::cerr << "[" << std::put_time(localtime(&now), "%Y-%m-%d %H:%M:%S") << "]\t[Base Packet]\t" << "Error: No field at " << index << ", max is " << packetStructure->numberOfFields() << ", returning nullopt" << std::endl; return std::nullopt; } } /** * @brief Returns a pointer to the memory in which stores the packet. * * This function returns a constant pointer to the memory. * * @return A constant pointer to the memory. */ uint8_t const* getPointerToMemory() const { return memoryPointer; } /** * @brief Copies data from a source memory location to the packet's memory. * * @param from Pointer to the source memory location. * @param size Number of bytes to copy. * @return int The number of bytes copied. */ int copyToMemory(const uint8_t *from, uint size) { uint max_writable = this->packetStructure->getByteSize(); if (size > max_writable) { time_t now = time(nullptr); std::cerr << "[" << std::put_time(localtime(&now), "%Y-%m-%d %H:%M:%S") << "]\t[Base Packet]\t" << "Error: you are trying to copy " << size << " byte where the max size is: " << max_writable << std::endl; std::cerr << "[" << std::put_time(localtime(&now), "%Y-%m-%d %H:%M:%S") << "]\t[Base Packet]\t" << "\tI copy only until " << max_writable << " byte" << std::endl; std::memcpy(memoryPointer, from, max_writable); return max_writable; } else { std::memcpy(memoryPointer, from, size); return size; } } /** * Copies data from the given memory location to the packet's memory buffer. * * @param from Pointer to the source memory location. * @param size Number of bytes to copy. * @param offset Offset in the packet's memory buffer to start copying to. * @return The number of bytes copied. */ int copyToMemory(const uint8_t *from, uint size, uint offset) { uint max_writable = this->packetStructure->getByteSize(); if (offset > max_writable) { time_t now = time(nullptr); std::cerr << "[" << std::put_time(localtime(&now), "%Y-%m-%d %H:%M:%S") << "]\t[Base Packet]\t" << "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) { time_t now = time(nullptr); std::cerr << "[" << std::put_time(localtime(&now), "%Y-%m-%d %H:%M:%S") << "]\t[Base Packet]\t" << "Error: you are trying to copy " << size + offset << " byte where the max size is: " << max_writable << std::endl; std::cerr << "[" << std::put_time(localtime(&now), "%Y-%m-%d %H:%M:%S") << "]\t[Base Packet]\t" << "\tI copy only until " << max_writable << " byte" << std::endl; int to_write = max_writable - offset; std::memcpy(&memoryPointer[offset], from, to_write); return to_write; } else { std::memcpy(&memoryPointer[offset], from, size); return size; } } /** * @brief Accesses the value at the specified index in the packet. * * @param index The index of the value to access. * @return An optional containing the value at the specified index, if it exists. */ std::optional operator[](uint index) const { return readValueFromMemoryAt(index); } /** * @brief Accesses the value of a field in the packet by its field name. * * @param fieldName The name of the field to access. * @return An optional value of the specified field type. If the field does not exist, the optional will be empty. */ std::optional operator[](std::string fieldName) const { std::transform(fieldName.begin(), fieldName.end(), fieldName.begin(), [](unsigned char c) { return std::tolower(c); }); auto index = packetStructure->indexOfField(fieldName); if (index.has_value()) return readValueFromMemoryAt(index.value()); else return std::nullopt; } /** * @brief Returns a constant bit iterator pointing to the beginning of the packet data. * * @return A constant bit iterator pointing to the beginning of the packet data. */ bit_iterator begin() const { return bit_iterator(memoryPointer, 0, packetStructure, &_readValueFromMemoryAt_); } /** * @brief Returns a bit_iterator pointing to the end of the container. * * @return A bit_iterator pointing to the end of the container. */ bit_iterator end() const { return bit_iterator(memoryPointer, packetStructure->numberOfFields(), packetStructure, &_readValueFromMemoryAt_); } /** * Writes a value into memory at the specified index. * * @param index The index at which to write the value. * @param value The value to be written. * @return An optional containing number of fits written, if any. */ std::optional writeValueIntoMemoryAtIndex(uint index, ValueType value) { auto offset = this->packetStructure->bitOffsetOf(index); auto numbits = this->packetStructure->bitSizeOf(index); size_t min_req = minBitsRequired(value); if (!offset.has_value() || !numbits.has_value()) return std::nullopt; if (numbits < min_req) { time_t now = time(nullptr); std::cerr << "[" << std::put_time(localtime(&now), "%Y-%m-%d %H:%M:%S") << "]\t[Base Packet]\t" << "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: uint bitoffset = offset.value() % 8; // Calculate the byte offset from the bit offset: uint byteoffset = offset.value() / 8; uint numbits_written = 0; for (size_t i = 0; i < numbits; i++) { // Calculate the byte and bit index of the current bit: uint byte_index = byteoffset + (bitoffset + i) / 8; uint 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) { memoryPointer[byte_index] |= bit_mask; numbits_written++; } else { memoryPointer[byte_index] &= ~bit_mask; } } return numbits_written; } /** * @brief Get the byte size of the packet structure. * * @return The byte size of the packet structure. */ uint getPacketStructureByteSize() const { return packetStructure->getByteSize(); } /** * @brief Returns the packet structure of the BasePacket. * * @return A reference to the packet structure. */ BasePacketStructure& getPacketStructure() const { return *(this->packetStructure); } /** * @brief Checks if the packet has a recognized header. * @note This is a pure virtual function and must be implemented by derived classes. * @return true if the packet has a recognized header, false otherwise. */ virtual bool hasRecognizedHeader() const = 0; /** * @brief Checks if the passed data has a recognized header. * @param buff The data to check. * @note This is a pure virtual function and must be implemented by derived classes. * @return true if buff has a recognized header, false otherwise. */ virtual bool isRecognizedHeader(std::vector buff) const = 0; /** * @brief Get the size of the header of the packet. * @note This is a pure virtual function and must be implemented by derived classes. * @return The size of the header as an unsigned integer. */ virtual uint getHeaderSize() const = 0; /** * @brief Get the size of the payload of the packet. * @note This is a pure virtual function and must be implemented by derived classes. * @return The size of the payload as an unsigned integer. */ virtual uint getPayloadSize() const = 0; /** * @brief Get the size of the tail of the packet. * @note This is a pure virtual function and must be implemented by derived classes. * @return The size of the tail as an unsigned integer. */ virtual uint getTailSize() const = 0; }; using valueType = size_t; /** * @brief Represents a concrete class derived from BasePacketTempl with a specified value type. * * The `BasePacket` class is a concrete class derived from `BasePacketTempl` with a value type of `size_t`. */ class BasePacket: public BasePacketTempl { protected: public: BasePacket(BasePacketStructure &structure) : BasePacketTempl(structure) { } virtual ~BasePacket() = default; }; }