The PacketHandling library is a C++ framework designed for efficient network packet serialization, deserialization, and validation. It provides a type-safe, macro-driven approach to define and handle different packet types with built-in CRC validation and endianness handling.
All packets follow a standardized binary format:
[MAGIC][LENGTH][PACKET_ID][PAYLOAD][CRC]
- MAGIC: 8-byte magic number (
0xFEEDFACECAFEBEEF) for packet identification - LENGTH: 2-byte packet length (including CRC)
- PACKET_ID: 2-byte packet type identifier
- PAYLOAD: Variable-length packet-specific data
- CRC: 4-byte CRC-32 checksum for data integrity
IPacket (Interface)
└── BasePacket<T> (Template Base)
├── NoContentPacket Classes
└── OneArgPacket Classes
-
IPacket: Abstract interface defining packet contractgetPacketID(): Returns packet type identifierpacketToBuffer(): Serializes packet to binary buffer
-
BasePacket<T>: Template base class providing shared functionality- Inherits from
std::enable_shared_from_this<T> - Provides
getShared()method for safe shared pointer creation
- Inherits from
The library supports two categories of packets defined through macros:
Packets without payload data:
StartFlashPacketAlreadyFlashingPacketIssueStartingFlashingPacketIssueFlashingPacketFlashingSoftwarePacket
Packets with one data field:
PingPacket(uint32_t UniqueID)PongPacket(uint32_t UniqueID)TestbitPacket(uint32_t value)DataPacket(std::vectorstd::uint8_t Data)ReceivedDataPacket(uint32_t value)
The library uses a sophisticated macro system to generate packet classes automatically:
#define PACKET(name, enum_value, ...) enum_value,
enum PacketType {
EMPTY_PACKET_LIST
ONE_ARG_PACKET_LIST
};#define PACKET(name, e_name) \
class name : public BasePacket<name> {\
public:\
static constexpr PacketType getPacketID(){return PacketType::e_name;}\
explicit name() {\
type = name::getPacketID();\
}\
const packet_size_type packetToBuffer(packet_raw_type&) const override;\
static std::shared_ptr<name> create(packet_raw_type::iterator& current, packet_raw_type::iterator it_end) {\
auto result = std::make_shared<name>();\
return result;\
}\
};#define PACKET(name, e_name, data_type, instance) \
class name : public BasePacket<name> {\
data_type instance; \
public:\
static constexpr PacketType getPacketID(){ return PacketType::e_name;};\
explicit name() {\
type = name::getPacketID();\
}\
name (data_type instance) : name(){ \
this->instance = instance;\
}\
const data_type get##instance(){ return this->instance;}\
data_type& get##instance##Ref(){return this->instance;}\
const packet_size_type packetToBuffer(packet_raw_type&) const override;\
static std::shared_ptr<name> create(packet_raw_type::iterator& current, packet_raw_type::iterator it_end) {\
auto result = std::make_shared<name>();\
if(!packet_utility_v2::read(result->instance, current, it_end)) return nullptr;\
return result;\
}\
};The library uses macros to generate status enums for packet processing:
#define CHECK_STATUS_STRINGS \
CHK_STATUS(WAITING_LENGTH) \
CHK_STATUS(WAITING_DATA) \
CHK_STATUS(BAD_CRC) \
CHK_STATUS(EXECUTED_PACKET)\
CHK_STATUS(BAD_PACKET_ID) \
CHK_STATUS(PACKET_TOO_SMALL) \
CHK_STATUS(NULL_PTR_RETURN)\
CHK_STATUS(CRC_ISSUE) \
CHK_STATUS(UNABLE_TO_READ_MAGIC)\
CHK_STATUS(BAD_MAGIC_AND_NOT_FOUND)\
CHK_STATUS(BAD_MAGIC_AND_FOUND)\
CHK_STATUS(FOUND_NEW_PACKET)#define SEARCH_STATUS_STRINGS \
SRCH_STATUS(NOTHING)\
SRCH_STATUS(NO_MAGIC_FOUND)\
SRCH_STATUS(UNKNOWN_ERROR_READING_MAGIC)\
SRCH_STATUS(POSSIBLE_PACKET_FOUND_LENGTH_TOO_SMALL)\
SRCH_STATUS(BAD_PACKET_ID_LOOK_UP)\
SRCH_STATUS(UNABLE_TO_READ_CRC_LOOK_UP)\
SRCH_STATUS(BAD_CRC_LOOK_UP)\
SRCH_STATUS(GOOD_PACKET_FOUND)The PacketHandler class manages packet processing with the following key responsibilities:
- Buffer Management: Maintains an internal buffer for incoming data
- Packet Detection: Searches for valid packets using magic number
- Packet Validation: Verifies CRC and packet integrity
- Packet Construction: Creates packet objects from binary data
- Packet Serialization: Converts packet objects to binary format
void receiveData(const uint8_t* data, size_t size);
void receiveData(const std::vector<uint8_t>& data);std::tuple<CheckStatus, std::shared_ptr<IPacket>> checkPacket();
std::tuple<SearchStatus, packet_raw_type::iterator> searchPacket(...);std::vector<uint8_t> createPacket(std::shared_ptr<IPacket> packet);
std::vector<uint8_t> createPacket(const IPacket& packet);Provides type-safe serialization/deserialization for supported data types:
- Integers: int8_t, int16_t, int32_t, int64_t, uint8_t, uint16_t, uint32_t, uint64_t
- Containers: std::vector, std::string
- Endianness: Automatic network byte order conversion
template<typename T, typename Iterator>
bool read(T& data, Iterator& current, Iterator end);
template<typename T, typename Iterator, typename Iterator_end>
bool write(Iterator& iterator, T value, Iterator_end end);Comprehensive CRC implementation supporting multiple algorithms:
- CRC-8
- CRC-CCITT
- MODBUS
- CRC-16
- CRC-24
- CRC-32
- CRC-32-BZIP2
- CRC-64-Jones
#define ALGO(NAME, size, POLYNOMIAL, INITIAL_VALUE, FINAL_XOR_VALUE, REVERSED_DATA, REVERSED_OUT, WIDTH)- Raw data received via
receiveData() - Data appended to internal buffer
checkPacket()called to process available data
- Search for magic number in buffer
- Read packet length and validate
- Read packet ID and validate against known types
- Calculate and verify CRC
- Create packet object using appropriate constructor
- Start with magic number
- Reserve space for length field
- Write packet ID
- Serialize payload data
- Calculate and write CRC
- Update length field with final size
The library uses comprehensive status enums to report processing results:
- CheckStatus: For packet validation and processing
- SearchStatus: For packet discovery and recovery
- Magic Number Search: Automatically searches for valid packet boundaries
- Buffer Shifting: Removes invalid data to find valid packets
- CRC Validation: Ensures data integrity
Automatic endianness detection and conversion:
inline uint16_t htons(uint16_t hostshort) {
uint32_t data = 42;
if (*((uint8_t*)&data) == 42) {
// Little-endian: swap bytes
uint8_t* ptr = (uint8_t*)&hostshort;
std::swap(ptr[0], ptr[1]);
return hostshort;
}
return hostshort; // Big-endian: no swap needed
}- Teensy 4.1 Support: Conditional compilation for embedded targets
- Cross-Platform: Works on Windows, Linux, and embedded systems
To add a new packet type, modify PacketDefinition.h:
// For empty packet
#define EMPTY_PACKET_LIST \
PACKET(MyEmptyPacket, MY_EMPTY_PACKET)
// For single-argument packet
#define ONE_ARG_PACKET_LIST \
PACKET(MyDataPacket, MY_DATA_PACKET, uint32_t, Value)PacketHandler handler;
// Receive data
handler.receiveData(data, size);
// Process packets
auto [status, packet] = handler.checkPacket();
if (status == EXECUTED_PACKET) {
// Handle packet
if (auto ping = std::dynamic_pointer_cast<PingPacket>(packet)) {
uint32_t id = ping->getUniqueID();
}
}
// Create packets
auto ping = std::make_shared<PingPacket>(12345);
auto data = handler.createPacket(ping);- Uses
std::shared_ptrfor automatic memory management - Efficient buffer operations with
std::move - Minimal memory allocations during packet processing
- Compile-time type checking through templates
- Runtime type safety through dynamic casting
- Macro-generated code ensures consistency
- Pre-computed lookup tables for fast CRC calculation
- Template specialization for optimal performance
- Support for multiple CRC algorithms
The architecture is designed for easy extension:
- New Packet Types: Add to macro definitions
- New Data Types: Extend PacketUtility with new type support
- New CRC Algorithms: Add to CRC macro system
- Platform Support: Conditional compilation for new targets
This modular design ensures the library can adapt to various use cases while maintaining type safety and performance.