de-serialize ASCII to struct - c++

I have come up with the following structure to declare various formats if messages that are to be received from the network:
#include <stdint.h>
#include <iostream>
#include <string.h>
template<int T>
struct uint
{
static uint<T> create(uint64_t value)
{
uint<T> r = {value};
return r;
}
uint(uint64_t value)
{
v = value;
}
uint()
{}
uint<T>& operator =(uint64_t value)
{
v = value;
return *this;
}
operator uint64_t() const
{
return (uint64_t)v;
}
unsigned long long v:T;
}__attribute__((packed));
example:
typedef uint<5> second_t;
suppose one of the message formats (which are auto-generated via some process) is like this:
struct seconds
{
char _type;
second_t _second;
} __attribute__((packed));
Now suppose I would like to populate an instance of the above messahe using a string:
int main()
{
seconds ii;
const char *i = "123456";
// memset, memcpy,sprintf... ??? what to use here?
std::cout << ii._type << " " << ii._second << std::endl;
}
Given a stream 123456, I expect the instance of the seconds (ii) structure to have char ii._type = '1' and integer ii._second = 23456. But I dont know how to do that. Do you have a clue how i can do that? and do you have any suggestion how to improve the basic structure?
thanks

You have a number of easier and more reliable options available that require almost no work.
check out google protocol buffers (platform independent message serialisation and deserialisation): https://developers.google.com/protocol-buffers/
or boost::serialization - (probably faster, but not platform-independant) http://www.boost.org/doc/libs/1_58_0/libs/serialization/doc/index.html

Related

Class design - use optionals? variants? be opaque?

I want to have a class for PCI bus locations. For the sake of discussion, these come in three forms:
[domain]:[bus]:[device].[function]
[domain]:[bus]:[device]
[bus]:[device].[function]
and let's say each field is a non-negative integral value (let's even say unsigned just to make things simple).
I'm scratching my head regarding how to define this class. I could use std::optionals for the domain and function fields; but then, they're not both optional. I could use a variant with 3 types, but then I need to define separate types, which overlap a lot. I could just hold 4 unsigneds and a 3-value enum for which format is in effect - but that's quite a bit of hassle, and I'd need getter and to make the class opaque. Same thing if I try to use a union somehow.
It seems like every choice I make, it's going to be an iffy class. How can I minimize my displeasure with it?
Note: Any language standard version is ok for the answer, although I doubt C++20 would give you anything.
Building upon my comment, I was wondering if something like this could work:
enum class pci_format { domain_function, domain, function };
template <pci_format E> struct tag { };
class pci_location {
public:
pci_location (tag<pci_format::domain_function>, unsigned domain, unsigned bus,
unsigned device, unsigned function)
: format_(pci_format::domain_function)
, domain_(domain)
, bus_(bus)
, device_(device)
, function_(function)
{ }
// Repeat for other values of pci_format.
pci_format format () const { return format_; }
bool has_domain () const {
return (format_ == pci_format::domain_function)
or (format_ == pci_format::domain);
}
unsigned domain () const {
if (not has_domain()) { throw std::runtime_error("Domain not available."); }
return domain_;
}
// Repeat for other fields.
private:
pci_format format_;
unsigned domain_;
unsigned bus_;
unsigned device_;
unsigned function_
};
You would basically create a specific constructor for each PCI "format". Of course you could also store each unsigned as an std::optional<unsigned>, but that would force users to "dereference" each optional even if they knew for sure that it must contain a value.
One way or another, they'll have to check what "format" the location is in, so it seems to me that using an enum for this is more user friendly. Then users only have to check once and know exactly which fields are available.
I guess you could layer a visitor on top of all this so they can simply provide code to execute for each "format":
struct pci_location_visitor {
virtual void visit (tag<pci_format::domain_function>, pci_location const & obj) = 0;
// Repeat for other enum values.
};
// Add to pci_location:
void accept (pci_location_visitor & visitor) {
switch (format_) {
case pci_format::domain_function:
return visitor.visit(tag<pci_format::domain_function>{}, *this);
default: throw std::runtime_error("Format not supported for visitation.");
}
}
Then on top of that you could create a visitor that can be constructed from a bunch of callables, i.e. lambdas, so that this all can be used like below:
pci_location const & loc = getIt();
auto printSomething = make_pci_location_visitor(
[](tag<pci_format::domain_function>, pci_location const & e) { std::cout << e.domain(); }
, [](tag<pci_format::domain>, pci_location const & e) { std::cout << e.bus(); }
, [](tag<pci_format::function>, pci_location const & e) { std::cout << e.function(); }
);
loc.accept(printSomething);
For an example of how such a visitor could be constructed, see the overloaded class in the std::visit example on cppreference.com.
As requested in comments... given that I have no particular requirements how the users would prefer to use this class, given C++14, I would be doing something generic along the lines of:
#include <array>
#include <climits>
#include <iostream>
#include <stdexcept>
class pci_location_t {
public:
struct dbdf {
unsigned int domain;
unsigned int bus;
unsigned int device;
unsigned int function;
};
struct dbd {
unsigned int domain;
unsigned int bus;
unsigned int device;
};
struct bdf {
unsigned int bus;
unsigned int device;
unsigned int function;
};
pci_location_t(dbdf v) : domain(v.domain), bus(v.bus), device(v.device), function(v.function) {}
pci_location_t(dbd v) : domain(v.domain), bus(v.bus), device(v.device), function(INVALID) {}
pci_location_t(bdf v) : domain(INVALID), bus(v.bus), device(v.device), function(v.function) {}
template <typename dbdf_f, typename dbd_f, typename bdf_f>
auto visit(dbdf_f dbdf_fun, dbd_f dbd_fun, bdf_f bdf_fun) const {
if (domain == INVALID) {
if (function == INVALID) {
throw std::domain_error("Wrong PCI location format");
}
return bdf_fun(bdf{bus, device, function});
} else if (function == INVALID) {
return dbd_fun(dbd{domain, bus, device});
} else {
return dbdf_fun(dbdf{domain, bus, device, function});
}
}
private:
friend pci_location_t invalid_location();
pci_location_t() : domain(INVALID), bus(INVALID), device(INVALID), function(INVALID) {}
const static unsigned int INVALID = UINT_MAX;
unsigned int domain;
unsigned int bus;
unsigned int device;
unsigned int function;
};
pci_location_t invalid_location() { return pci_location_t{}; }
int main() {
std::array<pci_location_t, 4> locations = {
pci_location_t(pci_location_t::dbdf{1, 2, 3, 4}),
pci_location_t(pci_location_t::dbd{1, 2, 3}),
pci_location_t(pci_location_t::bdf{2, 3, 4}),
invalid_location()
};
try {
for (auto& l : locations) {
l.visit(
[] (auto dbdf) {
std::cout << dbdf.domain << ":" << dbdf.bus << ":" << dbdf.device << "." << dbdf.function << std::endl;
},
[] (auto dbd) {
std::cout << dbd.domain << ":" << dbd.bus << ":" << dbd.device << std::endl;
},
[] (auto bdf) {
std::cout << bdf.bus << ":" << bdf.device << "." << bdf.function << std::endl;
}
);
}
std::cout << "Done!" << std::endl;
} catch(const std::exception& e) {
std::cout << e.what() << std::endl;
}
return 0;
}
(you can check it on Coliru).
Feel free to use optionals or a separate format field if you don't like special values.
I'd make both the domain and the function optional (I don't really care how, as long as it's effective), and just enforce the only-one-missing condition as a class invariant. That is, only the functions that can change any of the fields need to perform the check and signal possible errors back to the user. No need to bloat your code with variants, or with dynamically interpreted unsigned int arrays. KISS.

Efficient Binary Serialization Of Mostly Basic Types

I'm trying to figure out the best approach for transferring some data over the network. Here is what i'm hoping to achieve:
The application runs and computes some data:
int w = 5;
float x = 4.736;
std::string y = "Some String.";
std::vector<int> z;
z.push_back(1);
z.push_back(2);
z.push_back(3);
Then we put it in a binary container:
BinaryContainer Data;
Data.Write(w);
Data.Write(x);
Data.Write(y);
Data.Write(z);
We then transfer it over the network:
SendData(Data.c_str());
And read it out on the other side:
BinaryContainer ReceivedData(IncomingData);
int w = ReceivedData.Read();
float x = ReceivedData.Read();
std::string y = ReceivedData.Read();
std::vector<int> z = ReceivedData.Read();
The example above outlines how the basic functionality from a high level perspective should work. I've looked at many different serialization libraries and none seem to fit quite right. I'm leaning towards learning how to write the functionality myself.
Endianness doesn't matter. The architecture that reads and writes data will never differ.
We only need to store binary data inside the container. The reading application and writing application is exclusively responsible for reading data in the same order it was written. Only basic types need to be written, no entire arbitrary classes or pointers to things. Most importantly overall the speed in which this occurs should be of the highest priority because once the data is formulated, we need to write it to the container, transfer it over the network, and read it on the other end as fast as possible.
Network transmission is currently being done using the low level WinSock RIO API and we're moving data from the application to the wire as fast as possible already. Transmission latency across the wire will always be a much higher and variable rate. The point at which we serialize our data before transmission is the next step in the chain to ensure we are wasting as little time as possible before getting our data out on the wire.
New packets will be received very quickly, and as such the ability to preallocate resources would be beneficial. For example:
Serializer DataHandler;
...
void NewIncomingPacket(const char* Data)
{
DataHandler.Reset();
DataHandler.Load(Data);
int x = DataHandler.Read();
float y = DataHandler.Read();
...
}
I'm looking for input from community experts on which direction to go here.
If you don't care about endianness and only want to serialize trivial types than a simple memcpy will be the fastest and also safe. Just memcpy into/out of the buffer when serializing/deserializing.
#include <iostream>
#include <vector>
#include <cstring>
#include <cstdint>
#include <type_traits>
#include <cstddef>
template <std::size_t CapacityV>
struct BinaryContainer
{
BinaryContainer() :
m_write(0),
m_read(0)
{
}
template <typename T>
void write(const std::vector<T>& vec)
{
static_assert(std::is_trivial_v<T>);
// TODO: check if access is valid
const std::size_t bytes = vec.size() * sizeof(T);
std::memcpy(m_buffer + m_write, vec.data(), bytes);
m_write += bytes;
}
template <typename T>
void write(T value)
{
static_assert(std::is_trivial_v<T>);
// TODO: check if access is valid
const std::size_t bytes = sizeof(T);
std::memcpy(m_buffer + m_write, &value, bytes);
m_write += bytes;
}
template <typename T>
std::vector<T> read(std::size_t count)
{
static_assert(std::is_trivial_v<T>);
// TODO: check if access is valid
std::vector<T> result;
result.resize(count);
const std::size_t bytes = count * sizeof(T);
std::memcpy(result.data(), m_buffer + m_read, bytes);
m_read += bytes;
return result;
}
template <typename T>
T read()
{
static_assert(std::is_trivial_v<T>);
// TODO: check if access is valid
T result;
const std::size_t bytes = sizeof(T);
std::memcpy(&result, m_buffer + m_read, bytes);
m_read += bytes;
return result;
}
const char* data() const
{
return m_buffer;
}
std::size_t size() const
{
return m_write;
}
private:
std::size_t m_write;
std::size_t m_read;
char m_buffer[CapacityV]; // or a dynamically sized equivalent
};
int main()
{
BinaryContainer<1024> cont;
{
std::vector<std::uint32_t> values = {1, 2, 3, 4, 5};
// probably want to make serializing size part of the vector serializer
cont.write(values.size());
cont.write(values);
}
{
auto size = cont.read<std::vector<std::uint32_t>::size_type>();
auto values = cont.read<std::uint32_t>(size);
for (auto val : values) std::cout << val << ' ';
}
}
Demo: http://coliru.stacked-crooked.com/a/4d176a41666dbad1
I've written seriously, an header-only fast C++ library that should do what you want :-)
It provides both a serializer and a de-serializer.
Serialized data is portable across different architectures and endianness. No external dependencies.
seriously::Packer<1024> packer; // a 1024 byte serialization buffer
int32_t value1 = 83656;
bool value2 = true;
int16_t value3 = -2345;
std::string value4("only an example");
double value5 = -6.736;
std::vector<int64_t> value6;
value6.push_back(42);
value6.push_back(11);
value6.push_back(93);
packer << value1 << value2 << value3 << value4 << value5 << value6;
std::cout << "packed size: " << packer.size() << std::endl;
// packer.data() contains the serialized data
int32_t restored1;
bool restored2;
int16_t restored3;
std::string restored4;
double restored5 = -6.736;
std::vector<int64_t> restored6;
packer >> restored1 >> restored2 >> restored3 >> restored4 >> restored5 >> restored6;
std::cout << "unpacked: " << restored1 << " " << (restored2 ? "t" : "f") << " " << restored3 << " " << restored4 << " " << restored5 << std::endl;
std::vector<int64_t>::const_iterator it;
for (it = restored6.begin(); it != restored6.end(); it++) {
std::cout << *it << std::endl;
}

Correct way of unpacking operation type from network application

I come from python world, and as a weekend project I decided to write a simple UDP server in c++. I have a question regarding correct way of discovering the type of incoming request. My approach is to have a class for every possible type of request. Upon packet arrival I have to unpack it's OPID (operation id) and instantiate correct class. To do that I have to bind OPIDs with the classes, and the only way I'm familiar of doing this in c++ involves huge switch:case block. Doing this doesn't really feels right for me, also If I understand UncleBob correctly, this goes against few OOP practices. As code describes the best one's intentions, here's python equivalent of what I'm trying to do with c++.
class BaseOperation:
OPID = 0
def process(packet_data):
raise NotImplementedError("blah blah")
class FooOperation(BaseOperation):
OPID = 1
def process(packet_data):
print("Foo on the packet!")
class BarOperation(BaseOperation):
OPID = 2
def process(packet_data):
print("Bar on the packet!")
opid_mappings = {
FooOperation.OPID: FooOperation,
BarOperation.OPID: BarOperation
}
Somewhere in code handling the incoming packet
def handle_connection(packet):
try:
operation = opid_mappings[get_opid(packet)]()
except KeyError:
print("Unknown OPID")
return
operation.process(get_data(packet))
Really quick hack of object-based solution. This might not be the right way to go in our wonderful new C++11 world of std::function.
If the children of BaseOperation need to store state, go objects!
#include <iostream>
#include <map>
class BaseOperation
{
protected:
int OPID;
public:
virtual ~BaseOperation()
{
}
virtual int operator()() = 0;
};
class FooOperation:public BaseOperation
{
public:
static constexpr int OPID = 1;
FooOperation()
{
}
int operator()()
{
// do parsing
return OPID; // just for convenience so we can tell who was called
}
};
constexpr int FooOperation::OPID; // allocate storage for static
class BarOperation:public BaseOperation
{
public:
static constexpr int OPID = 2;
BarOperation()
{
}
int operator()()
{
// do parsing
return OPID; // just for convenience so we can tell who was called
}
};
constexpr int BarOperation::OPID; // allocate storage for static
std::map<int, BaseOperation*> opid_mappings{
{FooOperation::OPID, new FooOperation()},
{BarOperation::OPID, new BarOperation()}
};
int main()
{
std::cout << "calling OPID 1:" << (*opid_mappings[1])() << std::endl;
std::cout << "calling OPID 2:" << (*opid_mappings[2])() << std::endl;
for (std::pair<int, BaseOperation*> todel: opid_mappings)
{
delete todel.second;
}
return 0;
}
This also ignores the fact that there is probably no need for the map. If the OPIDs are sequential, a good ol' dumb array solves the problem. I like the map because it won't screw up if someone moves a parser handler or inserts one into the middle of the list.
Regardless, this has a bunch of memory management problems, such as the need for the for loop deleting the parser objects at the bottom of main. This could be solved with std::unique_ptr, but this is probably a rabbit hole we don't need to go down.
Odds are really good that the parser doesn't have any state and we can just use a map of OPIDs and std::function.
#include <iostream>
#include <map>
#include <functional>
static constexpr int FooOPID = 1;
int fooOperation()
{
// do parsing
return FooOPID;
}
static constexpr int BarOPID = 2;
int BarOperation()
{
// do parsing
return BarOPID;
}
std::map<int, std::function<int()>> opid_mappings {
{FooOPID, fooOperation},
{BarOPID, BarOperation}
};
int main()
{
std::cout << "calling OPID 1:" << opid_mappings[1]() << std::endl;
std::cout << "calling OPID 2:" << opid_mappings[2]() << std::endl;
return 0;
}
And because the parser's are kind of useless if you aren't passing anything in, one last tweak:
#include <iostream>
#include <map>
#include <functional>
struct Packet
{
//whatever you need here. Probably a buffer reference and a length
};
static constexpr int FooOPID = 1;
int fooOperation(Packet & packet)
{
// do parsing
return FooOPID;
}
static constexpr int BarOPID = 2;
int BarOperation(Packet & packet)
{
// do parsing
return BarOPID;
}
std::map<int, std::function<int(Packet &)>> opid_mappings {
{FooOPID, fooOperation},
{BarOPID, BarOperation}
};
int main()
{
Packet packet;
std::cout << "calling OPID 1:" << opid_mappings[1](packet) << std::endl;
std::cout << "calling OPID 2:" << opid_mappings[2](packet) << std::endl;
return 0;
}

Char array in a struct - not renewing?

I have a for-loop and i'm creating a new instance of a struct on the stack each time. This struct just contains 2 variables - 2 char arrays of 64 bytes.
The code is below:
for (std::map<std::string, std::string>::iterator iter = m_mDevices.begin(); iter != m_mDevices.end(); ++iter)
{
Structs::SDeviceDetails sRecord;
if (false == GenerateDeviceCacheRecord(iter->first, iter->second, sRecord)) // could just pass iter in?
{
// Failed to create cache record
return false;
}
}
The really strange thing i am seeing in the debugger, is everytime i loop round, i am seeing the same value in sRecord's buffers. i.e. sRecord.m_strUsername and sRecord.m_strPassword is getting "written over" as opposed to being a newly created struct.
If sRecord.m_strUsername was "abc" on the first loop round, then after the GenerateDeviceCacheRecord function (which just modifies sRecord), sRecord.m_strUsername might be "HIc", where c is the character off the first loop! I'm obviously expecting "abc" and "HI", not "abc" and "HIc". Does anyone know what might be going on here?
Thanks
Extra code:
namespace Constants
{
static const int64 MAX_HOSTNAME_BUFFER = 64;
static const int64 MAX_ILA_BUFFER = 64;
};
struct SDeviceRecordDetails
{
char m_strHostname[Constants::MAX_HOSTNAME_BUFFER];
char m_strILA[Constants::MAX_ILA_BUFFER];
};
bool GenerateDeviceCacheRecord(std::string strHostname, std::string strILA, Structs::SDeviceRecordDetails& sRecord)
{
// Convert strings to char arrays to store in the authentication cache manager records
if (strHostname.length() > Constants::MAX_HOSTNAME_BUFFER)
return false;
if (strILA.length() > Constants::MAX_ILA_BUFFER)
return false;
std::copy(strHostname.begin(), strHostname.end(), sRecord.m_strHostname);
std::copy(strILA.begin(), strILA.end(), sRecord.m_strILA);
return true;
}
//! #brief Devices retrieved from XML file
std::map<std::string, std::string> m_mDevicesAuthenticated;
So. I appreciate that you tried to get closer to a better question. So I'm going to take some next steps with you.
What you posted wasn't really a mcve.
Here's a mcve for your problem:
#include <iostream>
#include <cstdint>
#include <map>
#include <string>
#include <algorithm>
namespace Constants
{
static const int64_t MAX_HOSTNAME_BUFFER = 64;
static const int64_t MAX_ILA_BUFFER = 64;
};
struct SDeviceRecordDetails
{
char m_strHostname[Constants::MAX_HOSTNAME_BUFFER];
char m_strILA[Constants::MAX_ILA_BUFFER];
};
bool GenerateDeviceCacheRecord(std::string strHostname, std::string strILA, SDeviceRecordDetails& sRecord)
{
// Convert strings to char arrays to store in the authentication cache manager records
if (strHostname.length() > Constants::MAX_HOSTNAME_BUFFER)
return false;
if (strILA.length() > Constants::MAX_ILA_BUFFER)
return false;
std::copy(strHostname.begin(), strHostname.end(), sRecord.m_strHostname);
std::copy(strILA.begin(), strILA.end(), sRecord.m_strILA);
return true;
}
std::map<std::string, std::string> m_mDevices;
int main() {
m_mDevices["hello"] = "foo";
m_mDevices["buzz"] = "bear";
for (std::map<std::string, std::string>::iterator iter = m_mDevices.begin(); iter != m_mDevices.end(); ++iter) {
SDeviceRecordDetails sRecord;
const bool result = GenerateDeviceCacheRecord(iter->first, iter->second, sRecord);
if (result == false)
std::cout << "Failed\n";
else
std::cout << sRecord.m_strHostname << " " << sRecord.m_strILA << "\n";
}
}
Things to note:
I can take this as is (instead of two code blocks in your question) and throw it at a compiler.
I included the proper #include lines.
There were namespaces in your type names that weren't represented in your code.
m_mDevicesAuthenticated != m_mDevices.
You didn't include anything that actually had any output.
What is actually in m_mDevices? This is really important to include!
Among other small corrections I had to apply to the code to get it to build.
What did this code do?
This code almost produces the correct output. It has an error, in that the strings that are written to sRecord are not null terminated.
Because of how compilers generate code, and that you don't explicitly clear sRecord each loop, it's likely that this is the root cause of your problem.
Let's fix that:
Instead of:
std::copy(strHostname.begin(), strHostname.end(), sRecord.m_strHostname);
std::copy(strILA.begin(), strILA.end(), sRecord.m_strILA);
Let'd do:
snprintf(sRecord.m_strHostname, Constants::MAX_HOSTNAME_BUFFER, "%s", strHostname.c_str());
snprintf(sRecord.m_strILA, Constants::MAX_ILA_BUFFER, "%s", strILA.c_str());
Or perhaps you are concerned about what sRecord starts each loop with:
In this case, sRecord is not initialized at the beginning of each loop. The compiler is free to have junk data in the struct for optimization purposes.
It happens that most compilers will place each iteration of the struct in that exact same spot in memory. This means that the junk data in the struct could be the data from the previous iteration. Or some other junk depending on how the compiler optimizations function.
You could fix this by initializing the struct to contain explicit data:
SDeviceRecordDetails sRecord = {};
What does all of this look like:
The finished code, with all the bug fixes looks like:
#include <iostream>
#include <cstdint>
#include <map>
#include <string>
#include <algorithm>
namespace Constants
{
static const int64_t MAX_HOSTNAME_BUFFER = 64;
static const int64_t MAX_ILA_BUFFER = 64;
};
struct SDeviceRecordDetails
{
char m_strHostname[Constants::MAX_HOSTNAME_BUFFER];
char m_strILA[Constants::MAX_ILA_BUFFER];
};
bool GenerateDeviceCacheRecord(std::string strHostname, std::string strILA, SDeviceRecordDetails& sRecord)
{
// Convert strings to char arrays to store in the authentication cache manager records
if (strHostname.length() > Constants::MAX_HOSTNAME_BUFFER)
return false;
if (strILA.length() > Constants::MAX_ILA_BUFFER)
return false;
snprintf(sRecord.m_strHostname, Constants::MAX_HOSTNAME_BUFFER, "%s", strHostname.c_str());
snprintf(sRecord.m_strILA, Constants::MAX_ILA_BUFFER, "%s", strILA.c_str());
return true;
}
std::map<std::string, std::string> m_mDevices;
int main() {
m_mDevices["hello"] = "foo";
m_mDevices["buzz"] = "bear";
m_mDevices["zed"] = "zoo";
for (std::map<std::string, std::string>::iterator iter = m_mDevices.begin(); iter != m_mDevices.end(); ++iter) {
SDeviceRecordDetails sRecord = {};
const bool result = GenerateDeviceCacheRecord(iter->first, iter->second, sRecord);
if (result == false)
std::cout << "Failed\n";
else
std::cout << sRecord.m_strHostname << " " << sRecord.m_strILA << "\n";
}
}
And outputs:
buzz bear
hello foo
zed zoo
Which looks correct to my eyes.
I don't see any initialisation here. You're seeing whatever happened to be at that place in memory before, which for you, today, happens to be the previous contents of those data members.

Adding a string or char array to a byte vector

I'm currently working on a class to create and read out packets send through the network, so far I have it working with 16bit and 8bit integers (Well unsigned but still).
Now the problem is I've tried numerous ways of copying it over but somehow the _buffer got mangled, it segfaulted, or the result was wrong.
I'd appreciate if someone could show me a working example.
My current code can be seen below.
Thanks, Xeross
Main
#include <iostream>
#include <stdio.h>
#include "Packet.h"
using namespace std;
int main(int argc, char** argv)
{
cout << "#################################" << endl;
cout << "# Internal Use Only #" << endl;
cout << "# Codename PACKETSTORM #" << endl;
cout << "#################################" << endl;
cout << endl;
Packet packet = Packet();
packet.SetOpcode(0x1f4d);
cout << "Current opcode is: " << packet.GetOpcode() << endl << endl;
packet.add(uint8_t(5))
.add(uint16_t(4000))
.add(uint8_t(5));
for(uint8_t i=0; i<10;i++)
printf("Byte %u = %x\n", i, packet._buffer[i]);
printf("\nReading them out: \n1 = %u\n2 = %u\n3 = %u\n4 = %s",
packet.readUint8(),
packet.readUint16(),
packet.readUint8());
return 0;
}
Packet.h
#ifndef _PACKET_H_
#define _PACKET_H_
#include <iostream>
#include <vector>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
using namespace std;
class Packet
{
public:
Packet() : m_opcode(0), _buffer(0), _wpos(0), _rpos(0) {}
Packet(uint16_t opcode) : m_opcode(opcode), _buffer(0), _wpos(0), _rpos(0) {}
uint16_t GetOpcode() { return m_opcode; }
void SetOpcode(uint16_t opcode) { m_opcode = opcode; }
Packet& add(uint8_t value)
{
if(_buffer.size() < _wpos + 1)
_buffer.resize(_wpos + 1);
memcpy(&_buffer[_wpos], &value, 1);
_wpos += 1;
return *this;
}
Packet& add(uint16_t value)
{
if(_buffer.size() < _wpos + 2)
_buffer.resize(_wpos + 2);
memcpy(&_buffer[_wpos], &value, 2);
_wpos += 2;
return *this;
}
uint8_t readUint8()
{
uint8_t result = _buffer[_rpos];
_rpos += sizeof(uint8_t);
return result;
}
uint16_t readUint16()
{
uint16_t result;
memcpy(&result, &_buffer[_rpos], sizeof(uint16_t));
_rpos += sizeof(uint16_t);
return result;
}
uint16_t m_opcode;
std::vector<uint8_t> _buffer;
protected:
size_t _wpos; // Write position
size_t _rpos; // Read position
};
#endif // _PACKET_H_
Since you're using an std::vector for your buffer, you may as well let it keep track of the write position itself and avoid having to keep manually resizing it. You can also avoid writing multiple overloads of the add function by using a function template:
template <class T>
Packet& add(T value) {
std::copy((uint8_t*) &value, ((uint8_t*) &value) + sizeof(T), std::back_inserter(_buffer));
return *this;
}
Now you can write any POD type to your buffer.
implicitly:
int i = 5;
o.write(i);
or explictly:
o.write<int>(5);
To read from the buffer, you will need to keep track of a read position:
template <class T>
T read() {
T result;
uint8_t *p = &_buffer[_rpos];
std::copy(p, p + sizeof(T), (uint8_t*) &result);
_rpos += sizeof(T);
return result;
}
You will need to explicitly pass a type parameter to read. i.e.
int i = o.read<int>();
Caveat: I have used this pattern often, but since I am typing this off the top of my head, there may be a few errors in the code.
Edit: I just noticed that you want to be able to add strings or other non-POD types to your buffer. You can do that via template specialization:
template <>
Packet& add(std::string s) {
add(string.length());
for (size_t i = 0; i < string.length(); ++i)
add(string[i]);
return *this;
}
This tells the compiler: if add is called with a string type, use this function instead of the generic add() function.
and to read a string:
template <>
std::string read<>() {
size_t len = read<size_t>();
std::string s;
while (len--)
s += read<char>();
return s;
}
You could use std::string as internal buffer and use append() when adding new elements.
Thus adding strings or const char* would be trivial.
Adding/writing uint8 can be done with casting it to char, writing uint16 - to char* with length sizeof(uint16_t).
void write_uint16( uint16_t val )
{
m_strBuffer.append( (char*)(&var), sizeof(val) );
}
Reading uint16:
uint16_t read_int16()
{
return ( *(uint16_t*)(m_strBuffer.c_str() + m_nOffset) );
}
You appear to be attempting to print ten bytes out of the buffer when you've only added four, and thus you're running off the end of the vector. This could be causing your seg fault.
Also your printf is trying to print a character as an unsigned int with %x. You need to use static_cast<unsigned>(packet._buffer[i]) as the parameter.
Stylistically:
Packet packet = Packet(); could potentially result in two objects being constructed. Just use Packet packet;
Generally try to avoid protected attributes (protected methods are fine) as they reduce encapsulation of your class.