_
__(.)<
__\___)__
0 1 1
0 0 1
- MIT-licensed
- C++14
- Header-only
- constexpr
In C++, when we need to do some task that envolves some binary representation of types with more than one byte, is necessary the use of bitwise operator for convert the data in some endianess. One developer, for example, could be writing some software for TLS communication:
// ...
vector<unsigned char> stream { 0x16, 0x03, 0x01 }; // TLSv1 Protocol
// Header frame
// 0x16 - Handshake
// 0x0301 - SSL version (TLS 1.0)
vector<unsigned char> handshake_packet = make_client_hello();
// After header will have 2 big-endian bytes with the size of the frame
int16_t packet_size = static_cast<int16_t>(handshake_packet.size());
stream.push_back(packet_size >> 8);
stream.push_back(packet_size & 0x00FF);
// Merge the data and send over network
stream.insert(stream.end(), handshake_packet.begin(), handshake_packet.end());
send_over_wire(stream);
// ...
It is not clear, without the comments, what is been doing in the convertion to the 2 bytes of packet size. What is the endianess? What is the implict convertions of bitwise operations make?
Thinking in this need of reability and easy of development was made the lib witch permits made the convertion explicit, without losing performace.
// ...
// After header will have 2 big-endian bytes with the size of the frame
for (auto b : to_bin<int16_t>(handshake_packet.size(), endian::big))
stream.push_back(b);
// ...
The library defines two set of overload functions throught template specializations. They relay in a auxiliary type to represent the binary data conceptualy:
using byte_t = unsigned char;
This alias is just for convenience, while the C++ don't have a standart type for represent binary data, using this type is easy to print and express in code. Using the type as return is the function that converts integral types to a array of binary data:
template <class T>
constexpr std::array<byte_t, sizeof(T)> to_bin(T, endian = endian::native);
And another to convert a array of binary data to a integral type:
template <class T>
constexpr T bin_to(const std::array<byte_t, sizeof(T)>&, endian = endian::native);
The functions template are specialized for all integral types defined in
cstdint
: int8_t
, uint8_t
, int16_t
, uint16_t
, int32_t
, uint32_t
,
int64_t
and uint64_t
.
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>
#include "pato_bin.h"
using namespace std;
using namespace pato_bin;
int main()
{
string a { "fixed-length strings!!!" };
cout << a << "\n\n";
vector<byte_t> stream;
// Write size in the machine endian
for (auto b : to_bin<int8_t>(a.size()))
stream.push_back(b);
// Write string
for (auto ch : a)
stream.push_back(ch);
// Output the result
cout << hex << setfill('0') << uppercase;
for (auto b : stream)
cout << setw(2) << +b << ' ';
cout << dec << '\n';
for (auto c : stream)
cout << ' ' << static_cast<char>((isprint(c) ? c : ' ')) << ' ';
}
Prints:
fixed-length str!!!
13 66 69 78 65 64 2D 6C 65 6E 67 74 68 20 73 74 72 21 21 21
f i x e d - l e n g t h s t r ! ! !
Changing the to_bin<int8_t>(a.size())
to to_bin<int64_t>(a.size())
prints
(little-endian in this example):
fixed-length str!!!
13 00 00 00 00 00 00 00 66 69 78 65 64 2D 6C 65 6E 67 74 68 20 73 74 72 21 21 21
f i x e d - l e n g t h s t r ! ! !
// ...
constexpr int32_t i = 0x1A2B3C4D; // 0x1A'2B'3C'4D
constexpr auto binl_i = to_bin(i, endian::little); // array<byte_t, 4> { 0x4D, 0x3C, 0x2B, 0x1A };
constexpr auto binb_i = to_bin(i, endian::big); // array<byte_t, 4> { 0x1A, 0x2B, 0x3C, 0x4D };
// ...
// Revert
constexpr int32_t rl_i = bin_to<int32_t>(binl_i, endian::little); // rl_i == i
constexpr int32_t rb_i = bin_to<int32_t>(binb_i, endian::big); // rb_i == i
// ...
// Everything is constexpr
static_cast(i == rl_i, "equal");
static_cast(i == rb_i, "equal");