I am designing a class to store primitive types into byte buffer using predefined byte order. I am not going to use Boost.Serialization because I am working with plain types only and I need predefined binary structure without versions and things like that. Here is draft of the design:
#ifndef SERIALIZER_H_
#define SERIALIZER_H_
/**
* @file
*
* Set of byte-order independent value serializers.
* New serializers can be added with template class specialization.
*
*/
#include "types.hpp"
/**
* Serializer generic interface.
*/
template<typename ValueType>
struct serializer
{
/**
* Parses value from raw bytes.
*
* @param from byte buffer parse value from
*/
static ValueType parse(uint8_t *from);
/**
* Writes value to raw byte buffer.
*
* @param value value to write
* @param dest destination buffer
*/
static void write(ValueType value, uint8_t *dest);
};
/**
* Serializer specialization for single byte.
*/
template<>
struct serializer<uint8_t>
{
static uint8_t parse(uint8_t *from)
{
return *from;
}
static void write(const uint8_t value, uint8_t *to)
{
*to = value;
}
};
/**
* Serializer specialization for 2-byte number.
*/
template<>
struct serializer<uint16_t>
{
static uint16_t parse(uint8_t *from)
{
return (uint16_t)from[0] << 8 | from[1];
}
static void write(const uint16_t value, uint8_t *to)
{
to[0] = (value >> 8);
to[1] = value & 0xff;
}
};
/**
* Serializer specialization for 4-byte number.
*/
template<>
struct serializer<uint32_t>
{
static uint32_t parse(uint8_t *from)
{
return from[0] << 24 | from[1] << 16 | from[2] << 8 | from[3];
}
static void write(const uint32_t value, uint8_t *to)
{
serializer<uint16_t>::write(value >> 16, to);
serializer<uint16_t>::write(value & 0xffff, to + 2);
}
};
/**
* Serializer specialization for 8-byte number.
*/
template<>
struct serializer<uint64_t>
{
static uint64_t parse(uint8_t *from)
{
const uint32_t high = serializer<uint32_t>::parse(from);
const uint32_t low = serializer<uint32_t>::parse(from + 4);
return ((uint64_t) high << 32) | low;
}
static void write(const uint64_t value, uint8_t *to)
{
serializer<uint32_t>::write(value >> 32, to);
serializer<uint32_t>::write(value & 0xffffffff, to + 4);
}
};
/**
* Packet abstraction.
*
* Packet has mutable head which moves on every read or write. Packet
* overloads left/right shift operators to allow
* serialization/deserialization operations chaining:
*
* unit8_t buffer[80];
* packet p(buffer);
* p << obj.field << obj.other_field;
* // now obj fields serialized in the buffer
*
* Packet uses serializers for actual serialization/deserialization
* logic.
*
* Single packet designed to be used for reading OR writing, not for
* both operations interchaged. There is no way to move packet head
* back.
*
* @see serializer
*/
class packet
{
public:
/**
* Constructs packet from byte buffer pointer.
*
* @param buf pointer to byte buffer
*/
packet(uint8_t *buf);
/**
* Writes value to a packet.
*
* @param p packet reference
* @param value value to write
* @return reference to initial packet
*/
template<typename ValueType>
friend packet &operator<<(packet &p, ValueType value);
/**
* Reads value from a packet.
*
* @param p packet reference
* @param target value buffer reference
* @return reference to initial packet
*/
template<typename ValueType>
friend packet &operator>>(packet &p, ValueType &target);
/**
* Writes raw byte buffer to a packet.
*
* @param bytes bytes to write
* @param length buffer length
* @return reference to self
*/
packet &write_bytes(const uint8_t *bytes, std::size_t length);
/**
* Reads `length` buffers from a packet.
*
* @param dest target byte buffer
* @param length max buffer length
* @return reference to self
*/
packet &read_bytes(uint8_t *dest, std::size_t length);
/**
* Returns current head.
*
* @return current head
*/
uint8_t *get_head() const;
/**
* Skips `n` bytes in input buffer.
*/
packet &skip(std::size_t n);
/**
* Returns number of bytes written.
*
* @return number of bytes written
*/
off_t written() const;
/**
* Deallocates packet.
*/
~packet();
private:
uint8_t *_head; ///< current head
uint8_t *_start; ///< initial head
};
template<typename ValueType>
packet &operator<<(packet &p, ValueType value)
{
serializer<ValueType>::write(value, p._head);
p.skip(sizeof(ValueType));
return p;
}
template<typename ValueType>
packet &operator>>(packet &p, ValueType &target)
{
target = serializer<ValueType>::parse(p._head);
p.skip(sizeof(ValueType));
return p;
}
#endif /* SERIALIZER_H_ */
Looks like it do the thing. What do you think about that?
native_to_networkandnetwork_to_nativefunctions for uintN_t types which delegate their work to native functions. I just googled a bit and found that there were proposal for something like that in the Boost, but it isn't currently there (and probably will never be). Thank you! – roman-kashitsyn Jan 18 at 12:13