DOC HOME SITE MAP MAN PAGES GNU INFO SEARCH PRINT BOOK
 
Developing distributed applications using ONC RPC and XDR

XDR library primitives

This section gives a synopsis of each XDR primitive. It starts with basic data types and moves on to constructed data types. Finally, XDR utilities are discussed. The interface to these primitives and utilities is defined in the include file <rpc/xdr.h>, which is automatically included by <rpc/rpc.h>.

Number filters

The XDR library provides primitives that translate between C numbers and their corresponding external representations. The primitives cover the set of numbers in:

   [signed, unsigned] * [short, int, long]
Specifically, the six primitives are:
   bool_t xdr_int(xdrs, ip)
           XDR *xdrs;
           int *ip;
   

bool_t xdr_u_int(xdrs, up) XDR *xdrs; unsigned *up;

bool_t xdr_long(xdrs, lip) XDR *xdrs; long *lip;

bool_t xdr_u_long(xdrs, lup) XDR *xdrs; ulong *lup;

bool_t xdr_short(xdrs, sip) XDR *xdrs; short *sip;

bool_t xdr_u_short(xdrs, sup) XDR *xdrs; ushort *sup;

The first parameter, xdrs, is an XDR stream handle. The second parameter is the address of the number that provides data to the stream or receives data from it.

All routines return TRUE if they complete successfully, and FALSE otherwise.

Floating-Point filters

The XDR library also provides primitive routines for C's floating-point types:

   bool_t xdr_float(xdrs, fp)
           XDR *xdrs;
           float *fp;
   

bool_t xdr_double(xdrs, dp) XDR *xdrs; double *dp;

The first parameter, xdrs, is an XDR stream handle. The second parameter is the address of the floating point number that provides data to the stream or receives data from it.

All routines return TRUE if they complete successfully, and FALSE otherwise.


NOTE: Because the numbers are represented in IEEE floating point, routines may fail when decoding a valid IEEE representation into a machine-specific representation, or vice-versa.

Enumeration filters

The XDR library provides a primitive for generic enumerations. The primitive assumes that a C enum has the same representation inside the machine as a C integer. The boolean type is an important instance of the enum. The external representation of a boolean is always one (TRUE) or zero (FALSE).

   #define bool_t    int
   #define FALSE       0
   #define TRUE        1
   

#define enum_t int

bool_t xdr_enum(xdrs, ep) XDR *xdrs; enum_t *ep;

bool_t xdr_bool(xdrs, bp) XDR *xdrs; bool_t *bp;

The second parameters, ep and bp, are addresses of the associated type that provides data to, or receives data from, the stream xdrs.

The routines return TRUE if they complete successfully, and FALSE otherwise.

No data

Occasionally, an XDR routine must be supplied to the RPC system, even when no data is passed or required. The library provides such a routine:

   bool_t xdr_void();  /* always returns TRUE */

Constructed data type filters

Constructed or compound data-type primitives require more parameters and perform more complicated functions than the primitives discussed above. This section includes primitives for strings, arrays, unions, and pointers to structures.

Constructed data-type primitives may use memory management. In many cases, memory is allocated when deserializing data with XDR_DECODE. Therefore, the XDR package must provide means to deallocate memory. This is done by an XDR operation, XDR_FREE.

The three XDR directional operations are XDR_ENCODE, XDR_DECODE, and XDR_FREE.

Strings

In C, a string is defined as a sequence of bytes terminated by a null byte, which is not considered when calculating string length. However, when a string is passed or manipulated, a pointer to it is used. Therefore, the XDR library defines a string to be a char *, and not a sequence of characters. The external representation of a string is drastically different from its internal representation. Externally, strings are represented as sequences of ASCII characters, while internally they are represented with character pointers. Conversion between the two representations is accomplished with the routine xdr_string():

   bool_t xdr_string(xdrs, sp, maxlength)
           XDR *xdrs;
           char **sp;
           uint maxlength;
The parameters operate as follows: The routine returns FALSE if the number of characters exceeds maxlength, and TRUE if it does not.

The behavior of xdr_string() is similar to the behavior of other routines discussed in this section. The direction XDR_ENCODE is easiest to understand. The parameter sp points to a string of a certain length; if it does not exceed maxlength, the bytes are serialized.

The effect of deserializing a string is subtle.

In the XDR_FREE operation, the string is obtained by dereferencing sp. If the string is not NULL, it is freed and *sp is set to NULL. In this operation, xdr_string ignores the maxlength parameter.

Byte arrays

Using variable-length arrays of bytes are often preferable to strings. Byte arrays differ from strings in the following ways:

The primitive xdr_bytes() converts between the internal and external representations of byte arrays:
   bool_t xdr_bytes(xdrs, bpp, lp, maxlength)
           XDR *xdrs;
           char **bpp;
           uint *lp;
           uint maxlength;
The usage of the first, second, and fourth parameters is identical to the first, second, and third parameters of xdr_string(), respectively. The length of the byte area is obtained by dereferencing lp when serializing; *lp is set to the byte length when deserializing.

Arrays

The XDR library package provides a primitive for handling arrays of arbitrary elements. The xdr_bytes() routine treats a subset of generic arrays in which the size of array elements is known to be 1 and the external description of each element is built in. The generic array primitive xdr_array() requires parameters identical to those of xdr_bytes() plus two more: the size of array elements and an XDR routine to handle each of the elements. This routine is called to encode or decode each element of the array.

   bool_t xdr_array(xdrs, ap, lp, maxlength, elementsize, xdr_element)
           XDR *xdrs;
           char **ap;
           uint *lp;
           uint maxlength;
           uint elementsize;
           bool_t (*xdr_element)();
The parameter ap is the address of the pointer to the array. If *ap is NULL when the array is being deserialized, XDR allocates an array of the appropriate size and sets *ap to that array. The element count of the array is obtained from *lp when the array is serialized; *lp is set to the array length when the array is deserialized. The parameter maxlength is the maximum number of elements that the array is allowed to have; elementsize is the byte size of each element of the array. (The C function sizeof() can be used to obtain this value.) The routine xdr_element is called to serialize, deserialize, or free each element of the array.

Examples

Using the data types presented so far, you can define a single user, define multiple users, and create a command history.

Defining a single user

A user on a networked machine can be identified by the following:

A structure with this information and its associated XDR routine could be coded as follows:
   struct netuser {
           char        *nu_machinename;
           int        nu_uid;
           uint        nu_glen;
           int        *nu_gids;
   };
   #define NLEN 255 /* machine names must be shorter than 256 chars */
   #define NGRPS 20 /* user cannot be a member of more than 20 groups */
   

bool_t xdr_netuser(xdrs, nup) XDR *xdrs; struct netuser *nup; { return (xdr_string(xdrs, &nup->nu_machinename, NLEN) && xdr_int(xdrs, &nup->nu_uid) && xdr_array(xdrs, &nup->nu_gids, &nup->nu_glen, NGRPS, sizeof (int), xdr_int)); }

Defining multiple users

You can implement a party of network users as an array of netuser structure. The declaration and its associated XDR routines are as follows:

   struct party {
           uint p_len;
           struct netuser *p_nusers;
   };
   #define PLEN 500 /* max number of users in a party */
   

bool_t xdr_party(xdrs, pp) XDR *xdrs; struct party *pp; { return (xdr_array(xdrs, &pp->p_nusers, &pp->p_len, PLEN, sizeof (struct netuser), xdr_netuser)); }

Creating command history

You can combine the well-known parameters to main(), argc and argv into a structure, then use an array of these structures to make up a history of commands. The declarations and XDR routines might look like:

   struct cmd {
           uint c_argc;
           char **c_argv;
   };
   #define ALEN 1000  /* args can be no longer than 1000 chars */
   #define NARGC 100  /* commands can have no more than 100 args */
   

struct history { uint h_len; struct cmd *h_cmds; }; #define NCMDS 75 /* history is no more than 75 commands */

bool_t xdr_wrap_string(xdrs, sp) XDR *xdrs; char **sp; { return (xdr_string(xdrs, sp, ALEN)); }

bool_t xdr_cmd(xdrs, cp) XDR *xdrs; struct cmd *cp; { return (xdr_array(xdrs, &cp->c_argv, &cp->c_argc, NARGC, sizeof (char *), xdr_wrap_string)); }

bool_t xdr_history(xdrs, hp) XDR *xdrs; struct history *hp; { return (xdr_array(xdrs, &hp->h_cmds, &hp->h_len, NCMDS, sizeof (struct cmd), xdr_cmd)); }

The routine xdr_wrap_string() is needed to package the xdr_string() routine because the implementation of xdr_array() only passes two parameters to the array element description routine; xdr_wrap_string() supplies the third parameter to xdr_string().

Constructed opaque data

In some protocols, handles are passed from server to client. The client passes the handle back to the server at some later time. Handles are never inspected by clients; they are obtained and submitted. That is to say, handles are opaque. The primitive xdr_opaque() is used for describing fixed sized, opaque bytes:

   bool_t xdr_opaque(xdrs, p, len)
           XDR *xdrs;
           char *p;
           uint len;
The parameter p is the location of the bytes; len is the number of bytes in the opaque object. By definition, the actual data contained in the opaque object is not machine portable.

Fixed-Sized arrays

The XDR library does not provide a primitive for fixed-sized arrays. (The primitive xdr_array() is for varying-length arrays.) You can rewrite the example to define a single user to use fixed-sized arrays in the following fashion:

   #define NLEN 255  /* machine names must be shorter than 256 chars */
   #define NGRPS 20  /* user cannot be a member of more than 20 groups */
   

struct netuser { char *nu_machinename; int nu_uid; int nu_gids[NGRPS]; };

bool_t xdr_netuser(xdrs, nup) XDR *xdrs; struct netuser *nup; { int i;

if (! xdr_string(xdrs, &nup->nu_machinename, NLEN)) return (FALSE); if (! xdr_int(xdrs, &nup->nu_uid)) return (FALSE); for (i = 0; i < NGRPS; i++) { if (! xdr_int(xdrs, &nup->nu_gids[i])) return (FALSE); } return (TRUE); }

Constructed discriminated unions

The XDR library supports discriminated unions. A discriminated union is a C union and an enum_t value that selects an ``arm'' of the union:

   struct xdr_discrim {
           enum_t value;
           bool_t (*proc)();
   };
   

bool_t xdr_union(xdrs, dscmp, unp, arms, defaultarm) XDR *xdrs; enum_t *dscmp; char *unp; struct xdr_discrim *arms; bool_t (*defaultarm)(); /* may equal NULL */

First, the routine translates the discriminant of the union located at *dscmp. The discriminant is always an enum_t. Next, the union located at *unp is translated. The parameter arms is a pointer to an array of xdr_discrim structures. Each structure contains an order pair of [value,proc]. If the union's discriminant is equal to the associated value, then the proc is called to translate the union. The end of the xdr_discrim structure array is denoted by a routine of value NULL (0). If the discriminant is not found in the arms array, then the defaultarm procedure is called, assuming it is non-NULL; otherwise, the routine returns FALSE.

Using constructed discriminated unions

Suppose the type of a union is an integer, a character pointer (a string), or a gnumbers structure. Also, assume the union and its current type are declared in a structure. The declaration is:

   enum utype { INTEGER=1, STRING=2, GNUMBERS=3 };
   

struct u_tag { enum utype utype; /* this is the union's discriminant */ union { int ival; char *pval; struct gnumbers gn; } uval; };

The following constructs and XDR procedure (de)serialize the discriminated union:
   struct xdr_discrim u_tag_arms[4] = {
           { INTEGER, xdr_int },
           { GNUMBERS, xdr_gnumbers }
           { STRING, xdr_wrap_string },
           { __dontcare__, NULL }
           /* always terminate arms with a NULL xdr_proc */
   }
   

bool_t xdr_u_tag(xdrs, utp) XDR *xdrs; struct u_tag *utp; { return (xdr_union(xdrs, &utp->utype, &utp->uval, u_tag_arms, NULL)); }

The routines were originally described in the following sections: Therefore, the value of the union's discriminant may legally take on only values listed in the u_tag_arms array. This example also demonstrates that the elements of the arm's array do not need to be sorted.

It should be noted that the values of the discriminant may be sparse, though in this example they are not. It is always good practice to explicitly assign integer values to each element of the discriminant's type. This practice both documents the external representation of the discriminant and guarantees that different C compilers emit identical discriminant values.

Pointers

In C, it is often convenient to put pointers to another structure within a structure. The primitive xdr_reference() makes it easy to serialize, deserialize, and free these referenced structures:

   bool_t xdr_reference(xdrs, pp, ssize, proc)
           XDR *xdrs;
           char **pp;
           uint ssize;
           bool_t (*proc)();

Parameter pp is the address of the pointer to the structure. Parameter ssize is the size in bytes of the structure, which you can obtain by using the C function sizeof(). Parameter proc is the XDR routine that describes the structure. When decoding data, storage is allocated if *pp is NULL.

There is no need for a primitive xdr_struct() to describe structures within structures, because pointers are always sufficient.

Note that xdr_reference() and xdr_array() are not interchangeable external representations of data.

Using pointers

Suppose there is a structure containing a person's name and a pointer to a gnumbers structure containing the person's gross assets and liabilities. The construct is:

   struct pgn {
           char *name;
           struct gnumbers *gnp;
   };
The corresponding XDR routine for this structure is:
   bool_t
   xdr_pgn(xdrs, pp)
           XDR *xdrs;
           struct pgn *pp;
   {
           if (xdr_string(xdrs, &pp->name, NLEN) &&
               xdr_reference(xdrs, &pp->gnp, sizeof(struct gnumbers),
               xdr_gnumbers))
                   return(TRUE);
           return(FALSE);
   }

Pointer semantics and XDR

In many applications, C programmers attach double meaning to the values of a pointer. Typically, the value NULL or zero means data is not needed, yet some application-specific interpretation applies. In essence, the C programmer is encoding a discriminated union efficiently by overloading the interpretation of the value of a pointer. For instance, in the following example, a NULL pointer value for gnp could indicate that the person's assets and liabilities are unknown. That is, the pointer value encodes two things: whether or not the data is known and, if it is known, where it is located in memory. Linked lists are an extreme example of the use of application-specific pointer interpretation.

The primitive xdr_reference() cannot and does not attach any special meaning to a NULL-value pointer during serialization. That is, passing an address of a pointer whose value is NULL to xdr_reference() when serialing data will most likely cause a memory fault and, on UNIX systems, a core dump for debugging.

As a programmer, you must expand all other pointers, that is, NULL-value pointers, into their specific semantics. This usually involves describing data with a two-armed discriminated union. One arm is used when the pointer is valid; the other is used when the pointer is invalid (NULL). The section ``Advanced topics -- linked lists'' shows an example (linked-lists encoding) that deals with invalid pointer interpretation.

Non-filter primitives

XDR streams can be manipulated with the primitives discussed in this section.

   uint xdr_getpos(xdrs)
           XDR *xdrs;
   

bool_t xdr_setpos(xdrs, pos) XDR *xdrs; uint pos;

xdr_destroy(xdrs) XDR *xdrs;

The routine xdr_getpos() returns an unsigned integer that describes the current position in the data stream.


NOTE: In some XDR streams, the returned value of xdr_getpos() is meaningless; the routine returns a -1 in this case, although -1 should be a legitimate value.

The routine xdr_setpos() sets a stream position to pos.


NOTE: In some XDR streams, setting a position is impossible; in such cases, xdr_setpos() returns FALSE. This routine also fails if the requested position is out of bounds. The definition of bounds varies from stream to stream.

The xdr_destroy() primitive destroys the XDR stream. Use of the stream after calling this routine is undefined.

XDR operation directions

At times, you may want to optimize XDR routines by taking advantage of the direction of the operation (XDR_ENCODE, XDR_DECODE, or XDR_FREE). The value xdrs->x_op always contains the direction of the XDR operation. Programmers are not encouraged to take advantage of this information, so no example is presented here. However, an example in the section ``Advanced topics -- linked lists'' demonstrates the usefulness of the xdrs->x_op field.


Next topic: XDR stream access
Previous topic: Implementation details

© 2003 Caldera International, Inc. All rights reserved.
SCO OpenServer Release 5.0.7 -- 11 February 2003