libplayerxdr


Detailed Description

Overview

When a player message is sent over a network, the message is formatted according to XDR (eXternal Data Representation), a standard for the description and encoding of data that is independent of the wordsize, byte-order or other details of any particular architecture. XDR specifies a set of types (e.g., int, float, char) and encodings for them. See the XDR RFC (http://www.faqs.org/rfcs/rfc1014.html) for details.

libplayerxdr is a C library that provides functions for translating between player C message structs and their XDR representations. Using other terminology, libplayerxdr is a marshalling/demarshalling library. By using libplayerxdr, application writers can avoid the inevitable bugs and annoyances of writing their own (de)marshalling code. Furthermore, because libplayerxdr is automatically generated from player.h, the (de)marshalling code in libplayerxdr is less likely to get out of sync with respect to the message structures than is manually-maintained code. The program that parses player.h can also be used to parse other header files, for example to generate XDR (de)marshalling code for user-defined messages. See below for usage.

libplayerxdr

Each player message is defined as a C struct in player.h. For each struct player_foo, libplayerxdr defines a single function of the following name and form:
   int player_foo_pack(void* buf, size_t buflen, player_foo_t* msg, int op);

Parameters:
buf The XDR-encoded buffer that is being encoded / decoded.
buflen Size of buf, in bytes.
msg Pointer to the C struct that is being encoded / decoded.
op Either PLAYERXDR_ENCODE or PLAYERXDR_DECODE
Returns:
On success, the length of the XDR-encoded buffer, and -1 otherwise (e.g., the buffer was not large enough).
This function will either pack (encode to XDR) or unpack (decode from XDR), depending on the last argument.

Encoding a message

When encoding a message, the caller is responsible for allocating enough space to buf to hold the XDR-encoded format of the message. The XDR-encoded message will be, at most, 4 times larger than the original struct, so it is sufficient to allocate a buffer that is 4 times the sizeof of the struct.

For example, if you have a message msg of type struct player_foo that you want to encode, you might do something like this:

char* xdrbuf;
int buflen;

// Allocate space for the encoded message.  XDR will inflate a structure
// by at most 4 times.
buflen = sizeof(struct player_foo) * 4;
xdrbuf = (char*)calloc(1, buflen);
assert(xdrbuf);

// Encode the message
if((buflen = player_foo_pack(xdrbuf, buflen, &msg, PLAYERXDR_ENCODE)) < 0)
{
  // Packing failed, probably because you didn't allocate
  // enough space to xdrbuf.
}
else
{
  // Packing succeeded; you might now, for example, write() xdrbuf onto a
  // socket.  The actual length of the encoded buffer is buflen.
}

Decoding a message

If you have received from the network a message xdrbuf, of length buflen, and you know it to be of type player_foo, you can decode it like so:
struct player_foo msg;

// Decode the message
if((buflen = player_foo_pack(xdrbuf, buflen, &msg, PLAYERXDR_DECODE)) < 0)
{
  // Unpacking failed, probably because the message wasn't long enough
}
else
{
  // Unpacking succeeded; you can now read the data from msg.
}

Types

The following primitive types are defined by XDR, and can be used in player messages. For each XDR type, some corresponding C types are given, along with the size of the type when XDR-encoded.

Nested structures are supported. Each field, whether it is a structure or a primitive type, is encoded in the order that it is declared in the structure definition.

Pointers are NOT supported. To be XDR-encoded, a message structure must contain all its data. An exception (of sorts) is the encoding of arrays, described next.

Arrays

One-dimensional arrays are supported. An array may contain either a primitive type, or a structure. Multi-dimensional arrays are not supported.

An array may either be fixed-length or variable-length. To declare a variable-length array named bar, the message structure must also contain an unsigned integer (uint32_t) field named bar_count. This field will contain the actual element count of the array when encoding and decoding. If there is no bar_count field, then the array bar will be encoded fixed-length.

Before encoding a structure with a variable-length array, you must fill in the corresponding _count field; that's the only way for the packing function to know how much of the array you're actually using.

In either case, the array must be declared in the message structure to occupy its maximum length. For example, the player_bumper_data structure contains a variable-length array of bumper values that can hold at most 32 such values:

#define PLAYER_BUMPER_MAX_SAMPLES ((uint8_t)32)
typedef struct player_bumper_data
{
  uint32_t bumpers_count;
  uint8_t bumpers[PLAYER_BUMPER_MAX_SAMPLES];
} player_bumper_data_t;
Three examples of fixed-length arrays can be seen in the player_fiducial_geom structure:
typedef struct player_fiducial_geom
{
  float pose[3];
  float size[2];  
  float fiducial_size[2];
} player_fiducial_geom_t;
In this structure, each array will always be its maximum length, so there is nothing to be gained by allowing for them to vary.

Character arrays (strings)

According to the XDR specification, arrays of characters (e.g., char foo[8]) should be encoded as a sequence of XDR-encoded characters, each occupying 4 bytes. We use a small optimization here: arrays of the following types:

are encoded as opaque XDR "byte arrays", using xdr_bytes(). The encoding uses just one byte per character.

Parsing user-defined messages

The functions in libplayerxdr are automatically generated by a Python script that parses player.h. This script can also be used to parse a header containing user-defined messages. The script, playerxdrgen.py, can be used like so:
  parse.py foo.h foopack.c foopack.h
This command will parse the header foo.h and define in foopack.c functions that (de)marshall all the structures found in foo.h. Prototypes for these functions will be written into foopack.h. You could then compile foopack.c into libfoopack.a; any application needing to encode or decode the messages defined in foo.h would include <foopack.h> and link to libfoopack.a.

This usage of playerxdrgen.py is currently experimental.

Todo:
Make the interface code/string table dynamic, so that new interfaces can be added at runtime @}


Defines

#define XDR_ENCODE   0

Last updated 12 September 2005 21:38:45