Skip to content
Base_Packet.h 24.9 KiB
Newer Older
Valerio Pastore's avatar
Valerio Pastore committed

Valerio Pastore's avatar
Valerio Pastore committed
#pragma once
Valerio Pastore's avatar
Valerio Pastore committed

#include <vector>
Valerio Pastore's avatar
Valerio Pastore committed
#include <unordered_map>
Valerio Pastore's avatar
Valerio Pastore committed
#include <string>
#include <cstring>
#include <algorithm>
Valerio Pastore's avatar
Valerio Pastore committed
#include <numeric>
Valerio Pastore's avatar
Valerio Pastore committed
#include <iostream>
#include <cmath>
Valerio Pastore's avatar
Valerio Pastore committed
#include <functional>
Valerio Pastore's avatar
Valerio Pastore committed
#include <optional>
Valerio Pastore's avatar
Valerio Pastore committed
#include <ctime>
#include <iomanip>
Valerio Pastore's avatar
Valerio Pastore committed
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<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));
						  });
		}
Valerio Pastore's avatar
Valerio Pastore committed
		/**
		 * @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]);
Valerio Pastore's avatar
Valerio Pastore committed
		/**
		 * @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);
						  });
		}
Valerio Pastore's avatar
Valerio Pastore committed

Valerio Pastore's avatar
Valerio Pastore committed
		/**
		 * @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;
		}
Valerio Pastore's avatar
Valerio Pastore committed
	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);
		}
Valerio Pastore's avatar
Valerio Pastore committed
		/**
		 * @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;
		}
Valerio Pastore's avatar
Valerio Pastore committed
		/**
		 * @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);
		}
Valerio Pastore's avatar
Valerio Pastore committed
		/**
		 * @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;
		}

		/**
		 * @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 &;

		/**
		 * @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<ValueType(const uint8_t *, uint, uint)> m_func;

	private:
		const uint8_t *m_data;
		int m_offset;
		BasePacketStructure *m_structure;
		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;
		}
Valerio Pastore's avatar
Valerio Pastore committed

Valerio Pastore's avatar
Valerio Pastore committed
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 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 537 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 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 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 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 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 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819
		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. */

		/**
		 * @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<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;
		}

		/**
		 * @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<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_);
		}

		/**
		 * 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;
				}
			}
			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<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;
	};
}