Newer
Older
#include <string>
#include <cstring>
#include <algorithm>
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:
* 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<std::tuple<uint, std::string, uint>>; /**< 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<uint> fieldSizes; /**< vector of the sizes of the fields in the packet. */
std::unordered_map<uint, uint> indexToOffsetsMap; /**< Mapping of field index to field offset. */
std::unordered_map<std::string, uint> fieldNameToIndexMap; /**< Mapping of field name to field index. */
std::unordered_map<uint, std::string> indexToFieldNameMap; /**< Mapping of field index to field name. */
std::function<Structure(std::string)> 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<uint, std::string, uint> &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<uint, std::string, uint> &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<Structure(std::string)> 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<uint> 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<uint> 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<uint> 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<std::string> 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<std::string, uint> getFieldNameToIndexMap() const {
return this->fieldNameToIndexMap;
}
/**
* @brief Gets the index to offset map.
* @return The index to offset map.
*/
std::unordered_map<uint, uint> getIndexToOffsetsMap() const {
return this->indexToOffsetsMap;
}
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
/**
* @brief Gets the index to field name map.
* @return The index to field name map.
*/
std::unordered_map<uint, std::string> 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<typename ValueType>
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;
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
/**
* @brief Function to retrieve the value from data based on offset and size.
*/
std::function<ValueType(const uint8_t*, uint, uint)> m_func;
public:
bit_iterator(const uint8_t *data, int offset,
BasePacketStructure *structure,
std::function<ValueType(const uint8_t*, uint, uint)> 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<typename ValueType>
class BasePacketTempl {
protected:
BasePacketStructure *packetStructure; /**< Pointer to the structure defining the packet. */
uint8_t *memoryPointer; /**< Pointer to the memory containing packet data. */
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
/**
* @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;
* @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<ValueType> The value read from memory, or std::nullopt if the field does not exist.
*/
std::optional<ValueType> 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;
}
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
/**
* @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;
}
}
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
/**
* 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<ValueType> 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<ValueType> 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<ValueType> begin() const {
return bit_iterator<ValueType>(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<ValueType> end() const {
return bit_iterator<ValueType>(memoryPointer,
packetStructure->numberOfFields(), packetStructure,
&_readValueFromMemoryAt_);
}
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
/**
* 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<uint> 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;
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);
}
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
/**
* @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<uint8_t> 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<valueType> {
protected:
public:
BasePacket(BasePacketStructure &structure) :
BasePacketTempl<valueType>(structure) {
}
virtual ~BasePacket() = default;
};