Question

I want to design an application protocol, in which I define a class called message which has a fixed header size of 4 bytes, and variable payload part. To assign the payload to the message I want to do the following way:

 message msg;
 IPAddressv4 ipv4("127.0.0.1");
 IPAddressv6 ipv6("::1");
 Algorithm   alg(2);

 msg.version(1);
 msg.ack(1);

 msg << ipv4 & ipv6 & alg;

Where IPAddressv4, IPAddressv6 and Algorithm are defined object with basic data type. For example ipv4 is a uint32 and ipv6 is 2*unit64 and algorithm is uint8.

In this way the final message size is: Fixed Header Size "4" + 4 + 16 + 1 = 25 Bytes

So can anyone give me the general way how to implement this in c++, or is there anyway to do that in boost library?

Was it helpful?

Solution

So, I reckon you wanted a class that acts like a stream but with binary formatting.

Here's my simplistic interface for such a class (simplified):

struct Message
{
    // serialize all of the inserted buffer content to the given stream
    friend std::ostream& operator <<(std::ostream& os, Message const& msg);

    // read all of the given stream into the message buffer for extraction
    friend std::istream& operator >>(std::istream& is, Message const& msg);

    // insert integral data into the buffer, with fixed endianness
    template <typename Int>
        friend Message& operator <<(Message& msg, Int v);

    template <typename Int, size_t N>
        friend Message& operator <<(Message& msg, Int const (&v)[N]);

    // extract integral data from the buffer, with fixed endianness
    template <typename Int>
        friend Message& operator >>(Message& msg, Int& v);

    template <typename Int, size_t N>
        friend Message& operator >>(Message& msg, Int (&v)[N]);

    // default construction
    Message() : buffer { "\xee\x33" }
    {
        buffer.unsetf(std::ios::skipws);
    }

private:
    std::stringstream mutable buffer;
};

I left out the arcane details dealing with SFINAE/meta-programming. The constructor is quick and dirty, and shows a simplistic way to have a fixed message header (ee33 in this case).

Now you can use it like this:

std::string serialize_message()
{
    uint8_t  ipv4[4] = { 127, 0, 0, 1 };
    uint64_t ipv6[2] = { 0xdeadbeef00004b1d, 0xcafed00dcafebabe };
    uint8_t  alg     = 2;

    Message msg;
    msg << ipv4 << ipv6 << alg;

    // stringyfy
    std::stringstream ss;
    ss << msg;
    return ss.str();
}

int main()
{
    std::string const rep = serialize_message();
    std::cout << rep << std::flush; // for demo (view in hex)

    // reconstruct
    Message roundtrip;
    std::istringstream(rep) >> roundtrip;

    uint8_t  ipv4[4] = { 0 };
    uint64_t ipv6[2] = { 0 };
    uint8_t  alg     = 0;
    roundtrip >> ipv4 >> ipv6 >> alg;

    assert(rep == boost::lexical_cast<std::string>(roundtrip));
}

Note that the lexical_cast just uses the stream insertion operator listed above.

I've implemented this using Boost Spirit's binary parsers and generators.

The mapping of input types to parser/generator types are in a detail namespace.

See it Live On Coliru. Output:

0000000: ee33 7f00 0001 dead beef 0000 4b1d cafe  .3..........K...
0000010: d00d cafe babe 02                        .......

Full Code

#define LIVE_DANGEROUSLY
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/karma.hpp>
#include <boost/integer.hpp>
#include <sstream>

namespace detail
{
    using namespace boost::spirit;
    // meta programming helpers
    template <int bits, typename endianness = struct big> struct int_traits;

    template <typename endianness> struct int_traits<8, endianness/*ignored*/> { 
        typedef uint8_t                  type;
        typedef qi::byte_type            parser;
        typedef karma::byte_type         generator;
    };

    template <> struct int_traits<16, struct big> {
        typedef uint16_t                 type;
        typedef qi::big_word_type        parser;
        typedef karma::big_word_type     generator;
    };

    template <> struct int_traits<32, struct big> {
        typedef uint32_t                 type;
        typedef qi::big_dword_type       parser;
        typedef karma::big_dword_type    generator;
    };

    template <> struct int_traits<64, struct big> {
        typedef uint64_t                 type;
        typedef qi::big_qword_type       parser;
        typedef karma::big_qword_type    generator;
    };

    template <> struct int_traits<16, struct little> {
        typedef uint16_t                 type;
        typedef qi::little_word_type     parser;
        typedef karma::little_word_type  generator;
    };

    template <> struct int_traits<32, struct little> {
        typedef uint32_t                 type;
        typedef qi::little_dword_type    parser;
        typedef karma::little_dword_type generator;
    };

    template <> struct int_traits<64, struct little> {
        typedef uint64_t                 type;
        typedef qi::little_qword_type    parser;
        typedef karma::little_qword_type generator;
    };
}

struct Message
{
    friend std::ostream& operator <<(std::ostream& os, Message const& msg) {
        msg.buffer.seekg(0, std::ios_base::beg);
        return os << msg.buffer.rdbuf();
    }

    friend std::istream& operator >>(std::istream& is, Message const& msg) {
        msg.buffer.clear(); // this will overwrite our header
        msg.buffer.str("");
        return is >> msg.buffer.rdbuf();
    }

    template <typename Int>
    friend typename boost::enable_if<typename boost::is_integral<Int>::type, Message&>::type
        operator <<(Message& msg, Int v)
        {
            msg.buffer.seekp(0, std::ios_base::end);
            msg.append_int(v);
            return msg;
        }

    template <typename Int, size_t N>
    friend typename boost::enable_if<typename boost::is_integral<Int>::type, Message&>::type
        operator <<(Message& msg, Int const (&v)[N])
        {
            msg.buffer.seekp(0, std::ios_base::end);
            msg.append_int(v);
            return msg;
        }

    template <typename Int>
    friend typename boost::enable_if<typename boost::is_integral<Int>::type, Message&>::type
        operator >>(Message& msg, Int& v)
        {
            msg.extract_int(v);
            return msg;
        }

    template <typename Int, size_t N>
    friend typename boost::enable_if<typename boost::is_integral<Int>::type, Message&>::type
        operator >>(Message& msg, Int (&v)[N])
        {
            msg.extract_ints(v);
            return msg;
        }

    Message() : buffer { "\xee\x33" }
    {
        buffer.unsetf(std::ios::skipws);
    }

  private:
    std::stringstream mutable buffer;

    template <typename Int, int bits=std::numeric_limits<Int>::digits + std::numeric_limits<Int>::is_signed>
    void append_int(Int v)
    {
        namespace karma = boost::spirit::karma;
        static const typename detail::int_traits<bits>::generator gen {};
        karma::generate(std::ostreambuf_iterator<char>(buffer), gen, static_cast<typename detail::int_traits<bits>::type>(v));
    }

    template <typename Int, int bits=std::numeric_limits<Int>::digits + std::numeric_limits<Int>::is_signed>
    void extract_int(Int& v)
    {
        namespace qi = boost::spirit::qi;
        static const typename detail::int_traits<bits>::parser p {};
        boost::spirit::istream_iterator f(buffer), l;
        qi::parse(f, l, p, v);
    }

    template <typename Int, size_t N, int bits=std::numeric_limits<Int>::digits + std::numeric_limits<Int>::is_signed>
    void append_int(Int const(&v)[N])
    {
#ifdef LIVE_DANGEROUSLY // optimize, but implementation defined cast
        namespace karma = boost::spirit::karma;
        static const typename detail::int_traits<bits>::generator gen {};
        auto data = reinterpret_cast<typename detail::int_traits<bits>::type const*>(&v[0]);
        karma::generate(std::ostreambuf_iterator<char>(buffer), +gen, boost::make_iterator_range(data,data+N));
#else
        for(auto& i:v)
            append_int(i);
#endif
    }

    template <typename Int, size_t N, int bits=std::numeric_limits<Int>::digits + std::numeric_limits<Int>::is_signed>
    void extract_ints(Int (&v)[N])
    {
        for(auto& i:v)
            extract_int(i);
    }
};

std::string serialize_message()
{
    uint8_t  ipv4[4] = { 127, 0, 0, 1 };
    uint64_t ipv6[2] = { 0xdeadbeef00004b1d, 0xcafed00dcafebabe };
    uint8_t  alg     = 2;

    Message msg;
    msg << ipv4 << ipv6 << alg;

    // stringyfy
    std::stringstream ss;
    ss << msg;
    return ss.str();
}

int main()
{
    std::string const rep = serialize_message();
    std::cout << rep << std::flush; // for demo (view in hex)

    // reconstruct
    Message roundtrip;
    std::istringstream(rep) >> roundtrip;

    uint8_t  ipv4[4] = { 0 };
    uint64_t ipv6[2] = { 0 };
    uint8_t  alg     = 0;
    roundtrip >> ipv4 >> ipv6 >> alg;

    assert(rep == boost::lexical_cast<std::string>(roundtrip));
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top