The BytePack
guide covers its binary serialization capabilities, focusing on various data types, classes, and methods. It includes practical examples and addresses platform-specific concerns, offering solutions for consistent cross-platform serialization.
For installation, see Installation & Running Tests
- Fundamental types such as
char
,int
,double
, etc. (more) - Fixed width integer types such as
std::uint8_t
,std::int64_t
, etc. (more) - Enumerated types:
enum
,enum class
- Standard Containers:
std::vector
andstd::array
- Standard Strings:
std::string
andstd::string_view
- C-style arrays such as
char[]
,int[]
,double[]
, etc.
namespace: Library specific implementations (class, concept, etc.) are under
bytepack
namespace.
binary_stream
: The main class for serializing and deserializing data.buffer_view
: A non-owning mutable class that represents a buffer to provide an interface to access binary data without owning it. It encapsulates a pointer to data and its size. It does not manage the lifetime of the underlying data.
The binary_stream
class can be instantiated either with a non-owning buffer (buffer_view) or a specified size in bytes. When initialized with a size, the binary_stream autonomously allocates and manages the buffer's lifecycle. However, if it is constructed with a user-supplied buffer, it is the user's responsibility to ensure the proper deletion or freeing of the buffer.
Default buffer endianness is big-endian
. If you want to configure as little-endian
, instantiate as bytepack::binary_stream<std::endian::little> stream(..
- Create a stream with a new internal buffer of the specified size in bytes:
bytepack::binary_stream stream(1024);
- Create a stream using a user-supplied buffer (read buffer_view class section below):
bytepack::buffer_view buffer(ptr, size);
bytepack::binary_stream stream(buffer);
One method to serialize them all:
stream.write(var);
You can serialize multiple variables in a single call:stream.write(var1, var2, var3, ...);
- basic types:
stream.write(value);
- C-style arrays:
stream.write(arr);
- std::string and std:string_view:
- Serialize string with size prefixed (Prepends the string length. Default type of prefix is std::uint32_t):
stream.write(str);
- Serialize string with size prefixed. Specify type of prefix (Prepends the string length):
- e.g.
stream.write<std::uint8_t>(str);
- e.g.
- Serialize string with null terminator (Appends
'\0'
to the end of the string):stream.write<bytepack::StringMode::NullTerm>(str);
- Serialize string with given size (no size prefix):
- e.g.
stream.write<10>(str);
- Note-1: If the string size is greater than the given size, it will be truncated.
- Note-2: If the string size is less than the given size, it will be padded with
'\0'
.
- e.g.
- Serialize string with size prefixed (Prepends the string length. Default type of prefix is std::uint32_t):
- std::array:
stream.write(arr);
- std::vector:
- Serialize vector with size prefixed (Prepends the vector size. Default type of prefix is std::uint32_t):
stream.write(vec);
- Serialize vector with size prefixed. Specify type of prefix (Prepends the vector size):
- e.g.
stream.write<std::uint16_t>(vec);
- e.g.
- Serialize vector with fixed size (no size prefix):
- e.g.
stream.write<5>(vec);
- Note-1: If the vector size is greater than the given size, it will be truncated.
- Note-2: If the vector size is less than the given size, it won't be serialized and return false.
- e.g.
- Serialize vector with size prefixed (Prepends the vector size. Default type of prefix is std::uint32_t):
One method to deserialize them all:
stream.read(var);
You can deserialize multiple variables in a single call:stream.read(var1, var2, var3, ...);
- basic types:
stream.read(value);
- C-style arrays:
stream.read(arr);
- std::string and std:string_view:
- Deserialize size prefixed strings (default type of prefix is std::uint32_t):
stream.read(str);
- Deserialize size prefixed strings. Specify type of prefix:
- e.g.
stream.read<std::uint8_t>(str);
- e.g.
- Deserialize null terminated strings (reads until
'\0'
is reached. It won't exceed the buffer):stream.read<bytepack::StringMode::NullTerm>(str_);
- Deserialize fixed size strings:
- e.g.
stream.read<10>(str);
- Note-1: If the serialized string size is greater than the given read size, it will be truncated and later read calls will result in incorrect deserialization since the internal indices will be out of sync.
- Note-2: If the serialized string is padded with
'\0'
characters, they will be removed from the end of the string. This does not affect the internal indices and serialization/deserialization process. - Note-3: These Behaviors are applied only to std::string and std::string_view types. C-style strings are not affected by these behaviors.
- Note-4: You can also use
bytepack::StringMode::NullTerm
to read null terminatedC-style strings (char[])
asstd::string
type.
- e.g.
- Deserialize size prefixed strings (default type of prefix is std::uint32_t):
- std::array:
stream.read(arr);
- std::vector:
- Deserialize size prefixed vectors (default type of prefix is std::uint32_t):
stream.read(vec);
- Deserialize size prefixed vectors. Specify type of prefix:
- e.g.
stream.read<std::uint16_t>(vec);
- e.g.
- Deserialize fixed size vectors (no size prefix):
- e.g.
stream.read<5>(vec);
- e.g.
- Deserialize size prefixed vectors (default type of prefix is std::uint32_t):
data()
:- Returns a
bytepack::buffer_view
representing the current state of the internal buffer. - Example:
auto data = stream.data();
- You can access underlying data pointer and serialized data size with
as<T>()
andsize()
. Readbytepack::buffer_view
section below.
- You can access underlying data pointer and serialized data size with
- Returns a
reset()
:- The
reset
method in thebinary_stream
resets internal indices for serialization and deserialization. It's especially beneficial for network/socket communication. With reset, you can efficiently process both incoming and outgoing data without creating newbinary_stream
instances. This is useful for streaming data or handling multiple messages with the same buffer, optimizing resource usage and simplifying buffer management in networked applications.
- The
- From C-style Array (stack buffer):
char data[1024]{};
bytepack::buffer_view buffer(data);
- From pointer and size:
char* data = new char[1024]{};
bytepack::buffer_view buffer(data, 1024);
- From std::string:
std::string str = "...
bytepack::buffer_view buffer(str);
- From std::array:
std::array<char, 1024> arr{};
bytepack::buffer_view buffer(arr);
- or
bytepack::buffer_view buffer(arr.data(), size);
- From void pointer and size:
void* data = ...
bytepack::buffer_view buffer(data, size);
as<T>()
:- Template method to get a pointer to the buffer's data cast to the specified type T.
- Example:
char* ptr = buffer.as<char>();
size()
:- Returns the unsigned size of the buffer in bytes.
- Example:
std::size_t size = buffer.size();
ssize()
:- Returns the signed size of the buffer.
- Example:
std::ptrdiff_t ssize = buffer.ssize();
is_empty()
:- Checks if the buffer is empty (size is 0).
- Example:
bool empty = buffer.is_empty();
operator bool()
:- Checks if the buffer is valid (non-null and size greater than 0).
- Example:
if (buffer) { /* Buffer is valid */ }
Certain types in C++, such as std::size_t
, long int
, and unsigned long int
, can vary in size across different architectures and platforms. For instance, std::size_t
is 8 bytes on 64-bit systems but 4 bytes on 32-bit systems. Similarly, long int
and unsigned long int
are 8 bytes on Windows but 4 bytes on GNU/Linux systems, even on 64-bit platforms.
For consistent cross-platform compatibility, it's recommended to use fixed-width integer types
from <cstdint>
(e.g., std::uint32_t
, std::int64_t
) to ensure predictable data sizes during serialization and deserialization regardless of the underlying platform or architecture. This allows you to define your serialization standards with confidence, knowing your data will be interpreted accurately and consistently across different environments.
// Variables to serialize
int intArr[10]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
float num = 29845.221612f;
bytepack::binary_stream stream(1024);
stream.write(intArr);
stream.write(num);
bytepack::buffer_view buffer = stream.data();
char* dataPtr = buffer.as<char>();
std::size_t dataSize = buffer.size(); // total serialized data size (44 byte).
std::array<char, 1024 * 10> externalBuffer;
// ... other code to fill the buffer with data
// Data to deserialize into
int intArr_[10]{};
float num_{};
bytepack::binary_stream stream(bytepack::buffer_view(externalBuffer));
stream.read(intArr_);
stream.read(num_);
// Variables to serialize
int intArr[10]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
float num = 29845.221612f;
char data[64]{}; // or std::array<char, 64> data{};
bytepack::buffer_view buffer(data);
bytepack::binary_stream stream(buffer);
stream.write(intArr);
stream.write(num);
auto buffer = stream.data();
auto size = buffer.size(); // total serialized data size (44 byte).
struct SensorData {
std::int64_t timestamp; // UNIX timestamp of measurement
double value; // Measured value
char sensor_id[16]; // Identifier of the sensor
void serialize(bytepack::binary_stream<>& stream) const {
stream.write(timestamp, value, sensor_id);
}
void deserialize(bytepack::binary_stream<>& stream) {
stream.read(timestamp, value, sensor_id);
}
};
SensorData sensorData{ 1701037875, 23.6, "Sensor-001" };
bytepack::binary_stream serializationStream(64);
sensorData.serialize(serializationStream);
bytepack::buffer_view buffer = serializationStream.data();
bytepack::binary_stream deserializationStream(buffer);
SensorData sensorData_{};
sensorData_.deserialize(deserializationStream);
```cpp
enum class Type { TYPE_1, TYPE_2, TYPE_3, TYPE_4, TYPE_5 };
struct GPSData {
Type type; // Type of the data
std::uint32_t timestamp; // UNIX timestamp
double latitude; // Latitude in degrees
double longitude; // Longitude in degrees
float altitude; // Altitude in meters
std::uint16_t numSatellites; // Number of satellites in view
char deviceID[16]; // Device ID
void serialize(bytepack::binary_stream<>& stream) const {
stream.write(type);
stream.write(timestamp);
stream.write(latitude);
stream.write(longitude);
stream.write(altitude);
stream.write(numSatellites);
stream.write(deviceID);
}
void deserialize(bytepack::binary_stream<>& stream) {
stream.read(type);
stream.read(timestamp);
stream.read(latitude);
stream.read(longitude);
stream.read(altitude);
stream.read(numSatellites);
stream.read(deviceID);
}
};
GPSData gpsData{ Type::TYPE_4, 1701037875, 36.8805426411, 30.6692287448, 123.456f, 12, "GPS-DEVICE-1" };
bytepack::binary_stream serializationStream(1024);
gpsData.serialize(serializationStream);
auto buffer = serializationStream.data();
bytepack::binary_stream deserializationStream(buffer);
GPSData gpsData_{};
gpsData_.deserialize(deserializationStream);
struct SensorData {
std::int64_t timestamp; // UNIX timestamp of measurement
double value; // Measured value
char sensor_id[16]; // Identifier of the sensor
void serialize(bytepack::binary_stream<std::endian::little>& stream) const {
stream.write(timestamp, value, sensor_id);
}
void deserialize(bytepack::binary_stream<std::endian::little>& stream) {
stream.read(timestamp, value, sensor_id);
}
};
SensorData sensorData{ 1701037875, 23.6, "Sensor-001" };
bytepack::binary_stream<std::endian::little> serializationStream(64);
sensorData.serialize(serializationStream);
bytepack::buffer_view buffer = serializationStream.data();
bytepack::binary_stream<std::endian::little> deserializationStream(buffer);
SensorData sensorData_{};
sensorData_.deserialize(deserializationStream);