I am reverse engineering a proprietary packet based
protocol and working on a device to intercept it. For that I am receiving the packets one by one, storing them temporarily in a variable and then forwarding them.
But between storing and forwarding I also want to manipulate these packets and I can't really find an elegant way of doing this.
The most obvious way of course would be to store everything as a uint8_t array and then read/manipulate the individual bytes. But some of the data in some packets is stored in little endian and needs to be interpreted as float.
For the moment I have created this crazy overly complicates struct:
struct User {
uint8_t startSequence[2]; // 2 bytes for START SEQUENCE
uint8_t bodySize; // 1 byte for BODY SIZE
uint8_t type; // 1 byte for TYPE
uint8_t unknown; // 1 byte for UNKNOWN
uint8_t entityId; // 1 byte for ENTITY ID
uint8_t reserved[3]; // 3 bytes for RESERVED
std::vector<uint8_t> value; // Variable size for VALUE
uint8_t endSequence[2]; // 2 bytes for END SEQUENCE
User(const uint8_t* data, size_t length) {
if (length < 11) {
throw std::invalid_argument("Data too short to be a valid User packet");
}
// Parse header
startSequence[0] = data[0];
startSequence[1] = data[1];
bodySize = data[2];
type = data[3];
unknown = data[4];
if (type != Types::USER) {
throw std::invalid_argument("Invalid type for User packet");
}
// Parse body
entityId = data[5];
reserved[0] = data[6];
reserved[1] = data[7];
reserved[2] = data[8];
value.assign(data + 9, data + length - 2);
// Parse footer
endSequence[0] = data[length - 2];
endSequence[1] = data[length - 1];
if (startSequence[0] != START_SEQUENCE[0] || startSequence[1] != START_SEQUENCE[1]) {
throw std::invalid_argument("Invalid start sequence");
}
if (endSequence[0] != END_SEQUENCE[0] || endSequence[1] != END_SEQUENCE[1]) {
throw std::invalid_argument("Invalid end sequence");
}
}
/**
* This function returns the value of the packet as a boolean.
*/
bool getBoolValue() const {
if (value.size() != 1) {
throw std::invalid_argument("Value size is not 1");
}
return value[0] == 0x01;
}
/**
* This function returns the value of the packet as an unsigned 8-bit integer.
*/
uint8_t getUint8Value() const {
if (value.size() != 1) {
throw std::invalid_argument("Value size is not 1");
}
return value[0];
}
/**
* This function returns the value of the packet as a 32-bit float in little-endian format.
*/
float getFloatLeValue() const {
if (value.size() != 4) {
throw std::invalid_argument("Value size is not 4");
}
// Reorder the bytes to form the correct little-endian format
uint32_t temp = (value[0]) |
(value[1] << 8) |
(value[2] << 16) |
(value[3] << 24);
float result;
memcpy(&result, &temp, sizeof(result));
return result;
}
};
And even though this works, I am rsther dissatisfied with it because I'm sure there is a way cleaner way of doing this.
Ideally I would be able to be able to just map my data packet into the struct and be able to do sth like
User* packet = reinterpret_cast<User*>(data);
packet->value = 3.518;
In a way that the original byte array is correctly updated with the little endian representation for these 4 bytes.

