Writing XDR data to an expanding buffer
The other day I was implementing support for XDR insertion/extraction in the C++/Tree mapping. XDR is a binary representation format that allows you to store, move, and then extract your data without worrying about word sizes (32 vs 64 bit), endian-ness, etc. XDR is available out of the box on pretty much every UNIX and GNU/Linux system as part of Sun RPC.
To test the performance of my implementation I was first serializing a large object model to a memory buffer and then deserializing it from that buffer. You can easily create an XDR stream to read/write the data from/to a fixed-size memory buffer (xdrmem_create) or a standard I/O stream (xdrstdio_create). There is also the xdrrec_create function which supports a record-oriented serialization as well as an abstract, callback-based underlying buffer management. This function is the only option for serializing to a dynamically-expanding buffer short of creating your own XDR stream. Unfortunately there aren’t many examples that show how to use it so I had to figure out the correct usage myself.
The following code fragment shows how to read/write XDR data using the xdrrec_create function. We use std::vector<char> as a buffer:
#include <vector>
#include <cstring> // std::memcpy
#include <iostream>
#include <rpc/xdr.h>
using namespace std;
typedef vector<char> buffer;
extern "C" int
overflow (char* p, char* data, int n)
{
buffer* buf (reinterpret_cast<buffer*> (p));
size_t size (buf->size ());
buf->resize (size + n);
memcpy (buf->data () + size, data, n);
return n;
}
struct underflow_info
{
buffer* buf;
size_t pos;
};
extern "C" int
underflow (char* p, char* data, int n)
{
underflow_info* ui (reinterpret_cast<underflow_info*> (p));
size_t size (ui->buf->size () - ui->pos);
n = size > n ? n : size;
memcpy (data, ui->buf->data () + ui->pos, n);
ui->pos += n;
return n;
}
int
main ()
{
buffer buf;
// Serialize.
//
XDR oxdr;
xdrrec_create (&oxdr,
0,
0,
reinterpret_cast<char*> (&buf),
0,
&overflow);
oxdr.x_op = XDR_ENCODE;
unsigned int i (10);
xdr_uint32_t (&oxdr, &i);
xdrrec_endofrecord (&oxdr, true); // flush the data.
xdr_destroy (&oxdr);
cerr << "size: " << buf.size () << endl;
// Deserialize.
//
underflow_info ui;
ui.buf = &buf;
ui.pos = 0;
XDR ixdr;
xdrrec_create (&ixdr,
0,
0,
reinterpret_cast<char*> (&ui),
&underflow,
0);
ixdr.x_op = XDR_DECODE;
xdrrec_skiprecord (&ixdr);
i = 0;
xdr_uint32_t (&ixdr, &i);
xdr_destroy (&ixdr);
cerr << "i: " << i << endl;
}
The most non-obvious part in this code is the call to xdrrec_skiprecord.