CGx Bit Msg

A Lightweight C++ Library for Bit-Level Data Manipulation
Working with data protocols in network programming or embedded systems often requires manipulating data at the bit level. This can be tedious and error-prone. cgx_bit_msg is a C++14 header-only library designed to simplify this exact task. It provides a clean, declarative way to define the structure of bit-packed messages and safely encode or decode them.
This library is perfect for applications where memory layout and data size are critical. Being header-only, it’s incredibly easy to integrate into any C++ project using CMake.
Core Concepts
The library is built around two primary concepts:
field_t
: Represents a single field within a message. You define it with a data type, a specific number of bits, and optional validation rules.msg_t
: Represents a complete message, which is essentially a collection of field_t instances. It handles the marshalling (packing fields into a byte array) and unmarshalling (parsing a byte array into fields).
Example
Let’s look at a practical example from the repository. Suppose we have a 16-bit data packet that we need to decode. The first 4 bits represent a status code, and the next 12 bits represent a sensor value.
Here is how you would define and parse this structure using cgx_bit_msg
:
#include <array>
#include <iostream>
#include "../bit_msg.hpp"
int main() {
// Define the structure of our message
// A 4-bit field for the status
using status_f = cgx::bit::field_t<std::uint16_t, 4>;
// A 12-bit field for the value
using value_f = cgx::bit::field_t<std::uint16_t, 12>;
// Create a message definition
auto msg = cgx::bit::make_msg(
0, // Message ID
// Callback to execute on a valid message
[](const auto& decoded_msg) {
std::cout << "status_f: " << decoded_msg.template value_of<status_f>()
<< std::endl;
std::cout << "value_f: " << decoded_msg.template value_of<value_f>()
<< std::endl;
},
// Define fields and validation rules
status_f::between(0, 2), // Status must be 0, 1, or 2
value_f::any() // Any value is acceptable
);
// Some raw data to decode
// This one should be invalid because its status field is 4
std::array<std::uint8_t, 2> invalid_bytes{0x84, 0x0A};
// This one should be valid
std::array<std::uint8_t, 2> valid_bytes{0x80, 0x0A};
std::cout << "Unmarshalling invalid_bytes: ";
msg.unmarshal(invalid_bytes);
std::cout << (msg.is_valid() ? "valid" : "invalid") << std::endl;
// Expected output:
// Unmarshalling invalid_bytes: invalid
std::cout << "\nUnmarshalling valid_bytes: ";
msg.unmarshal(valid_bytes);
std::cout << (msg.is_valid() ? "valid" : "invalid") << std::endl;
// Expected output:
// Unmarshalling valid_bytes: status_f: 0
// value_f: 168
// valid
}
How It Works
The library processes the byte stream bit-by-bit, assuming a little-endian bit order by default. Let’s see why one input is valid and the other is not.
invalid_bytes {0x84, 0x0A}
In binary, this is 10000100 00001010
.
The status_f
field is 4 bits. The library reads the first 4 bits from the stream (LSB of the first byte). The bits are 0100
, which corresponds to the decimal value 4
.
This fails the validation rule status_f::between(0, 2)
. The message is flagged as invalid, and the callback is not executed.
valid_bytes {0x80, 0x0A}
In binary, this is 10000000 00001010
.
The library reads the first 4 bits for status_f
, which are 0000
. The value is 0
.
This passes the validation status_f::between(0, 2)
.
The library then proceeds to read the next 12 bits for value_f
. These bits span the rest of the first byte and all of the second byte.
The message is valid, so the callback is triggered, printing the decoded values (0
and 168
).
Get Started
CGx Bit Msg offers a powerful yet simple interface for bit-level data handling. Its declarative nature makes message formats easy to read and maintain.
To get started, simply include the headers in your C++14 project. For more details, check out the repository.