|
|
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>.
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;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.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;
All routines return TRUE
if they complete successfully, and
FALSE
otherwise.
The XDR library also provides primitive routines for C's floating-point types:
bool_t xdr_float(xdrs, fp) XDR xdrs; float fp;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.bool_t xdr_double(xdrs, dp) XDR xdrs; double dp;
All routines return TRUE if they complete successfully, and FALSE otherwise.
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 1The second parameters, ep and bp, are addresses of the associated type that provides data to, or receives data from, the stream xdrs.#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 routines return TRUE if they complete successfully, and FALSE otherwise.
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 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.
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 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.
Using variable-length arrays of bytes are often preferable to strings. Byte arrays differ from strings in the following ways:
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.
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.
Using the data types presented so far, you can define a single user, define multiple users, and create a command history.
A user on a networked machine can be identified by the following:
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)); }
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)); }
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 /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().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)); }
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.
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); }
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)(); };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.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 /
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 };The following constructs and XDR procedure (de)serialize the discriminated union:struct u_tag { enum utype utype; / this is the union's discriminant / union { int ival; char pval; struct gnumbers gn; } uval; };
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 / }The routines were originally described in the following sections: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)); }
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.
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.
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); }
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.
XDR streams can be manipulated with the primitives discussed in this section.
uint xdr_getpos(xdrs) XDR xdrs;The routine xdr_getpos() returns an unsigned integer that describes the current position in the data stream.bool_t xdr_setpos(xdrs, pos) XDR xdrs; uint pos;
xdr_destroy(xdrs) XDR xdrs;
The routine xdr_setpos() sets a stream position to pos.
The xdr_destroy() primitive destroys the
XDR stream.
Use of the stream after calling this routine is undefined.
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.