EExxtteerrnnaall DDaattaa RReepprreesseennttaattiioonn:: SSuunn TTeecchhnniiccaall NNootteess This chapter contains technical notes on Sun's implementa- tion of the External Data Representation (XDR) standard, a set of library routines that allow a C programmer to describe arbitrary data structures in a machinex-independent fashion. For a formal specification of the XDR standard, see the _E_x_t_e_r_n_a_l _D_a_t_a _R_e_p_r_e_s_e_n_t_a_t_i_o_n _S_t_a_n_d_a_r_d_: _P_r_o_t_o_c_o_l _S_p_e_c_i_f_i_c_a_t_i_o_n. XDR is the backbone of Sun's Remote Proce- dure Call package, in the sense that data for remote proce- dure calls is transmitted using the standard. XDR library routines should be used to transmit data that is accessed (read or written) by more than one type of machine.1 This chapter contains a short tutorial overview of the XDR library routines, a guide to accessing currently available XDR streams, and information on defining new streams and data types. XDR was designed to work across different lan- guages, operating systems, and machine architectures. Most users (particularly RPC users) will only need the informa- tion in the _N_u_m_b_e_r _F_i_l_t_e_r_s, _F_l_o_a_t_i_n_g _P_o_i_n_t _F_i_l_t_e_r_s, and _E_n_u_- _m_e_r_a_t_i_o_n _F_i_l_t_e_r_s sections. Programmers wishing to implement RPC and XDR on new machines will be interested in the rest of the chapter, as well as the _E_x_t_e_r_n_a_l _D_a_t_a _R_e_p_r_e_s_e_n_t_a_i_t_o_n _S_t_a_n_d_a_r_d_: _P_r_o_t_o_c_o_l _S_p_e_c_i_f_i_c_a_t_i_o_n, which will be their pri- mary reference. NNoottee:: _r_p_c_g_e_n _c_a_n _b_e _u_s_e_d _t_o _w_r_i_t_e _X_D_R _r_o_u_t_i_n_e_s _e_v_e_n _i_n _c_a_s_e_s _w_h_e_r_e _n_o _R_P_C _c_a_l_l_s _a_r_e _b_e_i_n_g _m_a_d_e_. On Sun systems, C programs that want to use XDR routines must include the file _<_r_p_c_/_r_p_c_._h_>, which contains all the necessary interfaces to the XDR system. Since the C library _l_i_b_c_._a contains all the XDR routines, compile as normal. example% cccc _p_r_o_g_r_a_m..cc ----------- 1 For a compete specification of the system External Data Representation routines, see the _x_d_r_(_3_N_) manual page. - 1 - Page 2 External Data Representation: Sun Technical Notes 11.. JJuussttiiffiiccaattiioonn Consider the following two programs, _w_r_i_t_e_r: #include main() /* _w_r_i_t_e_r_._c */ { long i; for (i = 0; i < 8; i++) { if (fwrite((char *)&i, sizeof(i), 1, stdout) != 1) { fprintf(stderr, "failed!\n"); exit(1); } } exit(0); } and _r_e_a_d_e_r: #include main() /* _r_e_a_d_e_r_._c */ { long i, j; for (j = 0; j < 8; j++) { if (fread((char *)&i, sizeof (i), 1, stdin) != 1) { fprintf(stderr, "failed!\n"); exit(1); } printf("%ld ", i); } printf("\n"); exit(0); } The two programs appear to be portable, because (a) they pass _l_i_n_t checking, and (b) they exhibit the same behavior when executed on two different hardware architectures, a Sun and a VAX. Piping the output of the _w_r_i_t_e_r program to the _r_e_a_d_e_r pro- gram gives identical results on a Sun or a VAX. sun% wwrriitteerr || rreeaaddeerr 0 1 2 3 4 5 6 7 sun% vax% wwrriitteerr || rreeaaddeerr 0 1 2 3 4 5 6 7 vax% With the advent of local area networks and 4.2BSD came the concept of "network pipes" -- a process produces data on one machine, and a second process consumes data on another External Data Representation: Sun Technical Notes Page 3 machine. A network pipe can be constructed with _w_r_i_t_e_r and _r_e_a_d_e_r. Here are the results if the first produces data on a Sun, and the second consumes data on a VAX. sun% wwrriitteerr || rrsshh vvaaxx rreeaaddeerr 0 16777216 33554432 50331648 67108864 83886080 100663296 117440512 sun% Identical results can be obtained by executing _w_r_i_t_e_r on the VAX and _r_e_a_d_e_r on the Sun. These results occur because the byte ordering of long integers differs between the VAX and the Sun, even though word size is the same. Note that 16777216 is 224 -- when four bytes are reversed, the 1 winds up in the 24th bit. Whenever data is shared by two or more machine types, there is a need for portable data. Programs can be made data- portable by replacing the _r_e_a_d_(_) and _w_r_i_t_e_(_) calls with calls to an XDR library routine _x_d_r___l_o_n_g_(_), a filter that knows the standard representation of a long integer in its external form. Here are the revised versions of _w_r_i_t_e_r: #include #include /* _x_d_r _i_s _a _s_u_b_-_l_i_b_r_a_r_y _o_f _r_p_c */ main() /* _w_r_i_t_e_r_._c */ { XDR xdrs; long i; xdrstdio_create(&xdrs, stdout, XDR_ENCODE); for (i = 0; i < 8; i++) { if (!xdr_long(&xdrs, &i)) { fprintf(stderr, "failed!\n"); exit(1); } } exit(0); } and _r_e_a_d_e_r: Page 4 External Data Representation: Sun Technical Notes #include #include /* _x_d_r _i_s _a _s_u_b_-_l_i_b_r_a_r_y _o_f _r_p_c */ main() /* _r_e_a_d_e_r_._c */ { XDR xdrs; long i, j; xdrstdio_create(&xdrs, stdin, XDR_DECODE); for (j = 0; j < 8; j++) { if (!xdr_long(&xdrs, &i)) { fprintf(stderr, "failed!\n"); exit(1); } printf("%ld ", i); } printf("\n"); exit(0); } The new programs were executed on a Sun, on a VAX, and from a Sun to a VAX; the results are shown below. sun% wwrriitteerr || rreeaaddeerr 0 1 2 3 4 5 6 7 sun% vax% wwrriitteerr || rreeaaddeerr 0 1 2 3 4 5 6 7 vax% sun% wwrriitteerr || rrsshh vvaaxx rreeaaddeerr 0 1 2 3 4 5 6 7 sun% NNoottee:: _I_n_t_e_g_e_r_s _a_r_e _j_u_s_t _t_h_e _t_i_p _o_f _t_h_e _p_o_r_t_a_b_l_e_-_d_a_t_a _i_c_e_- _b_e_r_g_. _A_r_b_i_t_r_a_r_y _d_a_t_a _s_t_r_u_c_t_u_r_e_s _p_r_e_s_e_n_t _p_o_r_t_a_b_i_l_i_t_y _p_r_o_b_- _l_e_m_s_, _p_a_r_t_i_c_u_l_a_r_l_y _w_i_t_h _r_e_s_p_e_c_t _t_o _a_l_i_g_n_m_e_n_t _a_n_d _p_o_i_n_t_e_r_s_. _A_l_i_g_n_m_e_n_t _o_n _w_o_r_d _b_o_u_n_d_a_r_i_e_s _m_a_y _c_a_u_s_e _t_h_e _s_i_z_e _o_f _a _s_t_r_u_c_- _t_u_r_e _t_o _v_a_r_y _f_r_o_m _m_a_c_h_i_n_e _t_o _m_a_c_h_i_n_e_. _A_n_d _p_o_i_n_t_e_r_s_, _w_h_i_c_h _a_r_e _v_e_r_y _c_o_n_v_e_n_i_e_n_t _t_o _u_s_e_, _h_a_v_e _n_o _m_e_a_n_i_n_g _o_u_t_s_i_d_e _t_h_e _m_a_c_h_i_n_e _w_h_e_r_e _t_h_e_y _a_r_e _d_e_f_i_n_e_d_. 22.. AA CCaannoonniiccaall SSttaannddaarrdd XDR's approach to standardizing data representations is _c_a_n_o_n_i_c_a_l. That is, XDR defines a single byte order (Big Endian), a single floating-point representation (IEEE), and so on. Any program running on any machine can use XDR to create portable data by translating its local representation to the XDR standard representations; similarly, any program running on any machine can read portable data by translating the XDR standard representaions to its local equivalents. The single standard completely decouples programs that External Data Representation: Sun Technical Notes Page 5 create or send portable data from those that use or receive portable data. The advent of a new machine or a new lan- guage has no effect upon the community of existing portable data creators and users. A new machine joins this community by being "taught" how to convert the standard representa- tions and its local representations; the local representa- tions of other machines are irrelevant. Conversely, to existing programs running on other machines, the local rep- resentations of the new machine are also irrelevant; such programs can immediately read portable data produced by the new machine because such data conforms to the canonical standards that they already understand. There are strong precedents for XDR's canonical approach. For example, TCP/IP, UDP/IP, XNS, Ethernet, and, indeed, all protocols below layer five of the ISO model, are canonical protocols. The advantage of any canonical approach is sim- plicity; in the case of XDR, a single set of conversion rou- tines is written once and is never touched again. The canonical approach has a disadvantage, but it is unimportant in real-world data transfer applications. Suppose two Lit- tle-Endian machines are transferring integers according to the XDR standard. The sending machine converts the integers from Little-Endian byte order to XDR (Big-Endian) byte order; the receiving machine performs the reverse conver- sion. Because both machines observe the same byte order, their conversions are unnecessary. The point, however, is not necessity, but cost as compared to the alternative. The time spent converting to and from a canonical represen- tation is insignificant, especially in networking applica- tions. Most of the time required to prepare a data struc- ture for transfer is not spent in conversion but in travers- ing the elements of the data structure. To transmit a tree, for example, each leaf must be visited and each element in a leaf record must be copied to a buffer and aligned there; storage for the leaf may have to be deallocated as well. Similarly, to receive a tree, storage must be allocated for each leaf, data must be moved from the buffer to the leaf and properly aligned, and pointers must be constructed to link the leaves together. Every machine pays the cost of traversing and copying data structures whether or not con- version is required. In networking applications, communica- tions overhead--the time required to move the data down through the sender's protocol layers, across the network and up through the receiver's protocol layers--dwarfs conversion overhead. 33.. TThhee XXDDRR LLiibbrraarryy The XDR library not only solves data portability problems, it also allows you to write and read arbitrary C constructs in a consistent, specified, well-documented manner. Thus, it can make sense to use the library even when the data is Page 6 External Data Representation: Sun Technical Notes not shared among machines on a network. The XDR library has filter routines for strings (null-termi- nated arrays of bytes), structures, unions, and arrays, to name a few. Using more primitive routines, you can write your own specific XDR routines to describe arbitrary data structures, including elements of arrays, arms of unions, or objects pointed at from other structures. The structures themselves may contain arrays of arbitrary elements, or pointers to other structures. Let's examine the two programs more closely. There is a family of XDR stream creation routines in which each member treats the stream of bits differently. In our example, data is manipulated using standard I/O routines, so we use _x_d_r_s_t_- _d_i_o___c_r_e_a_t_e(). The parameters to XDR stream creation rou- tines vary according to their function. In our example, _x_d_r_s_t_d_i_o___c_r_e_a_t_e_(_) takes a pointer to an XDR structure that it initializes, a pointer to a _F_I_L_E that the input or output is performed on, and the operation. The operation may be _X_D_R___E_N_C_O_D_E for serializing in the _w_r_i_t_e_r program, or _X_D_R___D_E_C_O_D_E for deserializing in the _r_e_a_d_e_r program. Note: RPC users never need to create XDR streams; the RPC system itself creates these streams, which are then passed to the users. The _x_d_r___l_o_n_g_(_) primitive is characteristic of most XDR library primitives and all client XDR routines. First, the routine returns _F_A_L_S_E (0) if it fails, and _T_R_U_E (1) if it succeeds. Second, for each data type, _x_x_x, there is an associated XDR routine of the form: xdr_xxx(xdrs, xp) XDR *xdrs; xxx *xp; { } In our case, _x_x_x is long, and the corresponding XDR routine is a primitive, _x_d_r___l_o_n_g_(_). The client could also define an arbitrary structure _x_x_x in which case the client would also supply the routine _x_d_r___x_x_x(), describing each field by call- ing XDR routines of the appropriate type. In all cases the first parameter, _x_d_r_s can be treated as an opaque handle, and passed to the primitive routines. XDR routines are direction independent; that is, the same routines are called to serialize or deserialize data. This feature is critical to software engineering of portable data. The idea is to call the same routine for either oper- ation -- this almost guarantees that serialized data can also be deserialized. One routine is used by both producer and consumer of networked data. This is implemented by External Data Representation: Sun Technical Notes Page 7 always passing the address of an object rather than the object itself -- only in the case of deserialization is the object modified. This feature is not shown in our trivial example, but its value becomes obvious when nontrivial data structures are passed among machines. If needed, the user can obtain the direction of the XDR operation. See the _X_D_R _O_p_e_r_a_t_i_o_n _D_i_r_e_c_t_i_o_n_s section below for details. Let's look at a slightly more complicated example. Assume that a person's gross assets and liabilities are to be exchanged among processes. Also assume that these values are important enough to warrant their own data type: struct gnumbers { long g_assets; long g_liabilities; }; The corresponding XDR routine describing this structure would be: bool_t /* _T_R_U_E _i_s _s_u_c_c_e_s_s_, _F_A_L_S_E _i_s _f_a_i_l_u_r_e */ xdr_gnumbers(xdrs, gp) XDR *xdrs; struct gnumbers *gp; { if (xdr_long(xdrs, &gp->g_assets) && xdr_long(xdrs, &gp->g_liabilities)) return(TRUE); return(FALSE); } Note that the parameter _x_d_r_s is never inspected or modified; it is only passed on to the subcomponent routines. It is imperative to inspect the return value of each XDR routine call, and to give up immediately and return _F_A_L_S_E if the subroutine fails. This example also shows that the type _b_o_o_l___t is declared as an integer whose only values are _T_R_U_E (1) and _F_A_L_S_E (0). This document uses the following definitions: #define bool_t int #define TRUE 1 #define FALSE 0 Keeping these conventions in mind, _x_d_r___g_n_u_m_b_e_r_s_(_) can be rewritten as follows: Page 8 External Data Representation: Sun Technical Notes xdr_gnumbers(xdrs, gp) XDR *xdrs; struct gnumbers *gp; { return(xdr_long(xdrs, &gp->g_assets) && xdr_long(xdrs, &gp->g_liabilities)); } This document uses both coding styles. 44.. XXDDRR LLiibbrraarryy PPrriimmiittiivveess 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 _<_r_p_c_/_x_d_r_._h_>, automatically included by _<_r_p_c_/_r_p_c_._h_>. 44..11.. NNuummbbeerr FFiilltteerrss The XDR library provides primitives to translate between numbers and their corresponding external representations. Primitives cover the set of numbers in: [signed, unsigned] * [short, int, long] Specifically, the eight primitives are: bool_t xdr_char(xdrs, cp) XDR *xdrs; char *cp; bool_t xdr_u_char(xdrs, ucp) XDR *xdrs; unsigned char *ucp; 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; u_long *lup; bool_t xdr_short(xdrs, sip) XDR *xdrs; short *sip; bool_t xdr_u_short(xdrs, sup) XDR *xdrs; u_short *sup; External Data Representation: Sun Technical Notes Page 9 The first parameter, _x_d_r_s, 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 _T_R_U_E if they complete successfully, and _F_A_L_S_E other- wise. 44..22.. FFllooaattiinngg PPooiinntt FFiilltteerrss 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, _x_d_r_s is an XDR stream handle. The sec- ond parameter is the address of the floating point number that provides data to the stream or receives data from it. Both routines return _T_R_U_E if they complete successfully, and _F_A_L_S_E otherwise. Note: Since the numbers are represented in IEEE floating point, routines may fail when decoding a valid IEEE repre- sentation into a machine-specific representation, or vice- versa. 44..33.. EEnnuummeerraattiioonn FFiilltteerrss The XDR library provides a primitive for generic enumera- tions. The primitive assumes that a C _e_n_u_m has the same representation inside the machine as a C integer. The boolean type is an important instance of the _e_n_u_m. The external representation of a boolean is always _T_R_U_E (1) or _F_A_L_S_E (0). #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 _e_p and _b_p are addresses of the associ- ated type that provides data to, or receives data from, the stream _x_d_r_s. Page 10 External Data Representation: Sun Technical Notes 44..44.. NNoo DDaattaa 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(); /* _a_l_w_a_y_s _r_e_t_u_r_n_s _T_R_U_E */ 44..55.. CCoonnssttrruucctteedd DDaattaa TTyyppee FFiilltteerrss Constructed or compound data type primitives require more parameters and perform more complicated functions then the primitives discussed above. This section includes primi- tives for strings, arrays, unions, and pointers to struc- tures. Constructed data type primitives may use memory management. In many cases, memory is allocated when deserializing data with _X_D_R___D_E_C_O_D_E Therefore, the XDR package must provide means to deallocate memory. This is done by an XDR opera- tion, _X_D_R___F_R_E_E To review, the three XDR directional opera- tions are _X_D_R___E_N_C_O_D_E, _X_D_R___D_E_C_O_D_E and _X_D_R___F_R_E_E. 44..55..11.. SSttrriinnggss 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 manipu- lated, a pointer to it is employed. Therefore, the XDR library defines a string to be a _c_h_a_r _* 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 representa- tions is accomplished with the routine _x_d_r___s_t_r_i_n_g(): bool_t xdr_string(xdrs, sp, maxlength) XDR *xdrs; char **sp; u_int maxlength; The first parameter _x_d_r_s is the XDR stream handle. The sec- ond parameter _s_p is a pointer to a string (type _c_h_a_r _*_*. The third parameter _m_a_x_l_e_n_g_t_h specifies the maximum number of bytes allowed during encoding or decoding. its value is usually specified by a protocol. For example, a protocol specification may say that a file name may be no longer than 255 characters. The routine returns _F_A_L_S_E if the number of characters exceeds _m_a_x_l_e_n_g_t_h, and _T_R_U_E if it doesn't. External Data Representation: Sun Technical Notes Page 11 KKeeeepp _m_a_x_l_e_n_g_t_h ssmmaallll.. IIff iitt iiss ttoooo bbiigg yyoouu ccaann bbllooww tthhee hheeaapp,, ssiinnccee _x_d_r___s_t_r_i_n_g_(_) wwiillll ccaallll _m_a_l_l_o_c_(_) ffoorr ssppaaccee.. The behavior of _x_d_r___s_t_r_i_n_g_(_) is similar to the behavior of other routines discussed in this section. The direction _X_D_R___E_N_C_O_D_E is easiest to understand. The parameter _s_p points to a string of a certain length; if the string does not exceed _m_a_x_l_e_n_g_t_h, the bytes are serialized. The effect of deserializing a string is subtle. First the length of the incoming string is determined; it must not exceed _m_a_x_l_e_n_g_t_h. Next _s_p is dereferenced; if the the value is _N_U_L_L, then a string of the appropriate length is allo- cated and _*_s_p is set to this string. If the original value of _*_s_p is non-null, then the XDR package assumes that a tar- get area has been allocated, which can hold strings no longer than _m_a_x_l_e_n_g_t_h. In either case, the string is decoded into the target area. The routine then appends a null character to the string. In the _X_D_R___F_R_E_E operation, the string is obtained by deref- erencing _s_p. If the string is not _N_U_L_L, it is freed and _*_s_p is set to _N_U_L_L. In this operation, _x_d_r___s_t_r_i_n_g_(_) ignores the _m_a_x_l_e_n_g_t_h parameter. 44..55..22.. BByyttee AArrrraayyss Often variable-length arrays of bytes are preferable to strings. Byte arrays differ from strings in the following three ways: 1) the length of the array (the byte count) is explicitly located in an unsigned integer, 2) the byte sequence is not terminated by a null character, and 3) the external representation of the bytes is the same as their internal representation. The primitive _x_d_r___b_y_t_e_s_(_) converts between the internal and external representations of byte arrays: bool_t xdr_bytes(xdrs, bpp, lp, maxlength) XDR *xdrs; char **bpp; u_int *lp; u_int maxlength; The usage of the first, second and fourth parameters are identical to the first, second and third parameters of _x_d_r___s_t_r_i_n_g(), respectively. The length of the byte area is obtained by dereferencing _l_p when serializing; _*_l_p is set to the byte length when deserializing. 44..55..33.. AArrrraayyss The XDR library package provides a primitive for handling arrays of arbitrary elements. The _x_d_r___b_y_t_e_s_(_) routine treats a subset of generic arrays, in which the size of Page 12 External Data Representation: Sun Technical Notes array elements is known to be 1, and the external descrip- tion of each element is built-in. The generic array primi- tive, _x_d_r___a_r_r_a_y_(_), requires parameters identical to those of _x_d_r___b_y_t_e_s_(_) 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, elementsiz, xdr_element) XDR *xdrs; char **ap; u_int *lp; u_int maxlength; u_int elementsiz; bool_t (*xdr_element)(); The parameter _a_p is the address of the pointer to the array. If _*_a_p is _N_U_L_L when the array is being deserialized, XDR allocates an array of the appropriate size and sets _*_a_p to that array. The element count of the array is obtained from _*_l_p when the array is serialized; _*_l_p is set to the array length when the array is deserialized. The parameter _m_a_x_l_e_n_g_t_h is the maximum number of elements that the array is allowed to have; _e_l_e_m_e_n_t_s_i_z is the byte size of each ele- ment of the array (the C function _s_i_z_e_o_f_(_) can be used to obtain this value). The _x_d_r___e_l_e_m_e_n_t_(_) routine is called to serialize, deserialize, or free each element of the array. Before defining more constructed data types, it is appropri- ate to present three examples. _E_x_a_m_p_l_e _A_: A user on a networked machine can be identified by (a) the machine name, such as _k_r_y_p_t_o_n: see the _g_e_t_h_o_s_t_n_a_m_e man page; (b) the user's UID: see the _g_e_t_e_u_i_d man page; and (c) the group numbers to which the user belongs: see the _g_e_t_g_r_o_u_p_s man page. A structure with this information and its associ- ated XDR routine could be coded like this: External Data Representation: Sun Technical Notes Page 13 struct netuser { char *nu_machinename; int nu_uid; u_int nu_glen; int *nu_gids; }; #define NLEN 255 /* _m_a_c_h_i_n_e _n_a_m_e_s _< _2_5_6 _c_h_a_r_s */ #define NGRPS 20 /* _u_s_e_r _c_a_n_'_t _b_e _i_n _> _2_0 _g_r_o_u_p_s */ 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)); } _E_x_a_m_p_l_e _B_: A party of network users could be implemented as an array of _n_e_t_u_s_e_r structure. The declaration and its associated XDR routines are as follows: struct party { u_int p_len; struct netuser *p_nusers; }; #define PLEN 500 /* _m_a_x _n_u_m_b_e_r _o_f _u_s_e_r_s _i_n _a _p_a_r_t_y */ 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)); } _E_x_a_m_p_l_e _C_: The well-known parameters to _m_a_i_n, _a_r_g_c and _a_r_g_v can be com- bined into a structure. An array of these structures can make up a history of commands. The declarations and XDR routines might look like: Page 14 External Data Representation: Sun Technical Notes struct cmd { u_int c_argc; char **c_argv; }; #define ALEN 1000 /* _a_r_g_s _c_a_n_n_o_t _b_e _> _1_0_0_0 _c_h_a_r_s */ #define NARGC 100 /* _c_o_m_m_a_n_d_s _c_a_n_n_o_t _h_a_v_e _> _1_0_0 _a_r_g_s */ struct history { u_int h_len; struct cmd *h_cmds; }; #define NCMDS 75 /* _h_i_s_t_o_r_y _i_s _n_o _m_o_r_e _t_h_a_n _7_5 _c_o_m_m_a_n_d_s */ 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 most confusing part of this example is that the routine _x_d_r___w_r_a_p___s_t_r_i_n_g_(_) is needed to package the _x_d_r___s_t_r_i_n_g_(_) rou- tine, because the implementation of _x_d_r___a_r_r_a_y_(_) only passes two parameters to the array element description routine; _x_d_r___w_r_a_p___s_t_r_i_n_g_(_) supplies the third parameter to _x_d_r___s_t_r_i_n_g(). By now the recursive nature of the XDR library should be obvious. Let's continue with more constructed data types. External Data Representation: Sun Technical Notes Page 15 44..55..44.. OOppaaqquuee DDaattaa In some protocols, handles are passed from a 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 _x_d_r___o_p_a_q_u_e_(_) primitive is used for describ- ing fixed sized, opaque bytes. bool_t xdr_opaque(xdrs, p, len) XDR *xdrs; char *p; u_int len; The parameter _p is the location of the bytes; _l_e_n is the number of bytes in the opaque object. By definition, the actual data contained in the opaque object are not machine portable. 44..55..55.. FFiixxeedd SSiizzeedd AArrrraayyss The XDR library provides a primitive, _x_d_r___v_e_c_t_o_r(), for fixed-length arrays. #define NLEN 255 /* _m_a_c_h_i_n_e _n_a_m_e_s _m_u_s_t _b_e _< _2_5_6 _c_h_a_r_s */ #define NGRPS 20 /* _u_s_e_r _b_e_l_o_n_g_s _t_o _e_x_a_c_t_l_y _2_0 _g_r_o_u_p_s */ 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); if (!xdr_vector(xdrs, nup->nu_gids, NGRPS, sizeof(int), xdr_int)) { return(FALSE); } return(TRUE); } 44..55..66.. DDiissccrriimmiinnaatteedd UUnniioonnss The XDR library supports discriminated unions. A discrimi- nated union is a C union and an _e_n_u_m___t value that selects an "arm" of the union. Page 16 External Data Representation: Sun Technical Notes 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)(); /* _m_a_y _e_q_u_a_l _N_U_L_L */ First the routine translates the discriminant of the union located at _*_d_s_c_m_p. The discriminant is always an _e_n_u_m___t. Next the union located at _*_u_n_p is translated. The parameter _a_r_m_s is a pointer to an array of _x_d_r___d_i_s_c_r_i_m structures. Each structure contains an ordered pair of _[_v_a_l_u_e_,_p_r_o_c_]. If the union's discriminant is equal to the associated _v_a_l_u_e, then the _p_r_o_c is called to translate the union. The end of the _x_d_r___d_i_s_c_r_i_m structure array is denoted by a routine of value _N_U_L_L (0). If the discriminant is not found in the _a_r_m_s array, then the _d_e_f_a_u_l_t_a_r_m procedure is called if it is non-null; otherwise the routine returns _F_A_L_S_E. _E_x_a_m_p_l_e _D_: Suppose the type of a union may be integer, char- acter pointer (a string), or a _g_n_u_m_b_e_r_s 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; /* _t_h_e _u_n_i_o_n_'_s _d_i_s_c_r_i_m_i_n_a_n_t */ union { int ival; char *pval; struct gnumbers gn; } uval; }; The following constructs and XDR procedure (de)serialize the discriminated union: External Data Representation: Sun Technical Notes Page 17 struct xdr_discrim u_tag_arms[4] = { { INTEGER, xdr_int }, { GNUMBERS, xdr_gnumbers } { STRING, xdr_wrap_string }, { __dontcare__, NULL } /* _a_l_w_a_y_s _t_e_r_m_i_n_a_t_e _a_r_m_s _w_i_t_h _a _N_U_L_L _x_d_r___p_r_o_c */ } 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 routine _x_d_r___g_n_u_m_b_e_r_s_(_) was presented above in _T_h_e _X_D_R _L_i_b_r_a_r_y section. _x_d_r___w_r_a_p___s_t_r_i_n_g_(_) was presented in example C. The default _a_r_m parameter to _x_d_r___u_n_i_o_n_(_) (the last parameter) is _N_U_L_L in this example. Therefore the value of the union's discriminant may legally take on only values listed in the _u___t_a_g___a_r_m_s array. This example also demon- strates that the elements of the arm's array do not need to be sorted. It is worth pointing out that the values of the discriminant may be sparse, though in this example they are not. It is always good practice to assign explicitly 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. Exercise: Implement _x_d_r___u_n_i_o_n_(_) using the other primitives in this section. 44..55..77.. PPooiinntteerrss In C it is often convenient to put pointers to another structure within a structure. The _x_d_r___r_e_f_e_r_e_n_c_e_(_) primitive makes it easy to serialize, deserialize, and free these ref- erenced structures. bool_t xdr_reference(xdrs, pp, size, proc) XDR *xdrs; char **pp; u_int ssize; bool_t (*proc)(); Parameter _p_p is the address of the pointer to the structure; parameter _s_s_i_z_e is the size in bytes of the structure (use the C function _s_i_z_e_o_f_(_) to obtain this value); and _p_r_o_c is the XDR routine that describes the structure. When decoding Page 18 External Data Representation: Sun Technical Notes data, storage is allocated if _*_p_p is _N_U_L_L. There is no need for a primitive _x_d_r___s_t_r_u_c_t_(_) to describe structures within structures, because pointers are always sufficient. Exercise: Implement _x_d_r___r_e_f_e_r_e_n_c_e_(_) using _x_d_r___a_r_r_a_y(). Warning: _x_d_r___r_e_f_e_r_e_n_c_e_(_) and _x_d_r___a_r_r_a_y_(_) are NOT inter- changeable external representations of data. _E_x_a_m_p_l_e _E_: Suppose there is a structure containing a per- son's name and a pointer to a _g_n_u_m_b_e_r_s 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); } _P_o_i_n_t_e_r _S_e_m_a_n_t_i_c_s _a_n_d _X_D_R In many applications, C programmers attach double meaning to the values of a pointer. Typically the value _N_U_L_L (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 example E a _N_U_L_L pointer value for _g_n_p 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 _x_d_r___r_e_f_e_r_e_n_c_e_(_) cannot and does not attach any special meaning to a null-value pointer during serializa- tion. That is, passing an address of a pointer whose value is _N_U_L_L to _x_d_r___r_e_f_e_r_e_n_c_e_(_) when serialing data will most likely cause a memory fault and, on the UNIX system, a core External Data Representation: Sun Technical Notes Page 19 dump. _x_d_r___p_o_i_n_t_e_r_(_) correctly handles _N_U_L_L pointers. For more information about its use, see the _L_i_n_k_e_d _L_i_s_t_s topics below. _E_x_e_r_c_i_s_e_: After reading the section on _L_i_n_k_e_d _L_i_s_t_s, return here and extend example E so that it can correctly deal with _N_U_L_L pointer values. _E_x_e_r_c_i_s_e_: Using the _x_d_r___u_n_i_o_n(), _x_d_r___r_e_f_e_r_e_n_c_e_(_) and _x_d_r___v_o_i_d_(_) primitives, implement a generic pointer handling primitive that implicitly deals with _N_U_L_L pointers. That is, implement _x_d_r___p_o_i_n_t_e_r(). 44..66.. NNoonn--ffiilltteerr PPrriimmiittiivveess XDR streams can be manipulated with the primitives discussed in this section. u_int xdr_getpos(xdrs) XDR *xdrs; bool_t xdr_setpos(xdrs, pos) XDR *xdrs; u_int pos; xdr_destroy(xdrs) XDR *xdrs; The routine _x_d_r___g_e_t_p_o_s_(_) returns an unsigned integer that describes the current position in the data stream. Warning: In some XDR streams, the returned value of _x_d_r___g_e_t_p_o_s_(_) is meaningless; the routine returns a -1 in this case (though -1 should be a legitimate value). The routine _x_d_r___s_e_t_p_o_s_(_) sets a stream position to _p_o_s. Warning: In some XDR streams, setting a position is impossi- ble; in such cases, _x_d_r___s_e_t_p_o_s_(_) will return _F_A_L_S_E. This routine will also fail if the requested position is out-of- bounds. The definition of bounds varies from stream to stream. The _x_d_r___d_e_s_t_r_o_y_(_) primitive destroys the XDR stream. Usage of the stream after calling this routine is undefined. 44..77.. XXDDRR OOppeerraattiioonn DDiirreeccttiioonnss At times you may wish to optimize XDR routines by taking advantage of the direction of the operation -- _X_D_R___E_N_C_O_D_E _X_D_R___D_E_C_O_D_E or _X_D_R___F_R_E_E The value _x_d_r_s_-_>_x___o_p always contains the direction of the XDR operation. Programmers are not encouraged to take advantage of this information. There- fore, no example is presented here. However, an example in the _L_i_n_k_e_d _L_i_s_t_s topic below, demonstrates the usefulness of the _x_d_r_s_-_>_x___o_p field. Page 20 External Data Representation: Sun Technical Notes 44..88.. XXDDRR SSttrreeaamm AAcccceessss An XDR stream is obtained by calling the appropriate cre- ation routine. These creation routines take arguments that are tailored to the specific properties of the stream. Streams currently exist for (de)serialization of data to or from standard I/O _F_I_L_E streams, TCP/IP connections and UNIX files, and memory. 44..88..11.. SSttaannddaarrdd II//OO SSttrreeaammss XDR streams can be interfaced to standard I/O using the _x_d_r_s_t_d_i_o___c_r_e_a_t_e_(_) routine as follows: #include #include /* _x_d_r _s_t_r_e_a_m_s _p_a_r_t _o_f _r_p_c */ void xdrstdio_create(xdrs, fp, x_op) XDR *xdrs; FILE *fp; enum xdr_op x_op; The routine _x_d_r_s_t_d_i_o___c_r_e_a_t_e_(_) initializes an XDR stream pointed to by _x_d_r_s. The XDR stream interfaces to the stan- dard I/O library. Parameter _f_p is an open file, and _x___o_p is an XDR direction. 44..88..22.. MMeemmoorryy SSttrreeaammss Memory streams allow the streaming of data into or out of a specified area of memory: #include void xdrmem_create(xdrs, addr, len, x_op) XDR *xdrs; char *addr; u_int len; enum xdr_op x_op; The routine _x_d_r_m_e_m___c_r_e_a_t_e_(_) initializes an XDR stream in local memory. The memory is pointed to by parameter _a_d_d_r; parameter _l_e_n is the length in bytes of the memory. The parameters _x_d_r_s and _x___o_p are identical to the corresponding parameters of _x_d_r_s_t_d_i_o___c_r_e_a_t_e(). Currently, the UDP/IP implementation of RPC uses _x_d_r_m_e_m___c_r_e_a_t_e(). Complete call or result messages are built in memory before calling the _s_e_n_d_t_o_(_) system routine. 44..88..33.. RReeccoorrdd ((TTCCPP//IIPP)) SSttrreeaammss A record stream is an XDR stream built on top of a record marking standard that is built on top of the UNIX file or External Data Representation: Sun Technical Notes Page 21 4.2 BSD connection interface. #include /* _x_d_r _s_t_r_e_a_m_s _p_a_r_t _o_f _r_p_c */ xdrrec_create(xdrs, sendsize, recvsize, iohandle, readproc, writeproc) XDR *xdrs; u_int sendsize, recvsize; char *iohandle; int (*readproc)(), (*writeproc)(); The routine _x_d_r_r_e_c___c_r_e_a_t_e_(_) provides an XDR stream interface that allows for a bidirectional, arbitrarily long sequence of records. The contents of the records are meant to be data in XDR form. The stream's primary use is for interfac- ing RPC to TCP connections. However, it can be used to stream data into or out of normal UNIX files. The parameter _x_d_r_s is similar to the corresponding parameter described above. The stream does its own data buffering similar to that of standard I/O. The parameters _s_e_n_d_s_i_z_e and _r_e_c_v_s_i_z_e determine the size in bytes of the output and input buffers, respectively; if their values are zero (0), then predetermined defaults are used. When a buffer needs to be filled or flushed, the routine _r_e_a_d_p_r_o_c_(_) or _w_r_i_t_e_p_r_o_c_(_) is called, respectively. The usage and behavior of these routines are similar to the UNIX system calls _r_e_a_d_(_) and _w_r_i_t_e(). However, the first parameter to each of these routines is the opaque parameter _i_o_h_a_n_d_l_e. The other two parameters _b_u_f and _n_b_y_t_e_s) and the results (byte count) are identical to the system routines. If _x_x_x is _r_e_a_d_p_r_o_c_(_) or _w_r_i_t_e_p_r_o_c(), then it has the following form: _/_* _* _r_e_t_u_r_n_s _t_h_e _a_c_t_u_a_l _n_u_m_b_e_r _o_f _b_y_t_e_s _t_r_a_n_s_f_e_r_r_e_d_. _* _-_1 _i_s _a_n _e_r_r_o_r _*_/ _i_n_t _x_x_x_(_i_o_h_a_n_d_l_e_, _b_u_f_, _l_e_n_) _c_h_a_r _*_i_o_h_a_n_d_l_e_; _c_h_a_r _*_b_u_f_; _i_n_t _n_b_y_t_e_s_; The XDR stream provides means for delimiting records in the byte stream. The implementation details of delimiting records in a stream are discussed in the _A_d_v_a_n_c_e_d _T_o_p_i_c_s topic below. The primitives that are specific to record streams are as follows: Page 22 External Data Representation: Sun Technical Notes bool_t xdrrec_endofrecord(xdrs, flushnow) XDR *xdrs; bool_t flushnow; bool_t xdrrec_skiprecord(xdrs) XDR *xdrs; bool_t xdrrec_eof(xdrs) XDR *xdrs; The routine _x_d_r_r_e_c___e_n_d_o_f_r_e_c_o_r_d_(_) causes the current outgoing data to be marked as a record. If the parameter _f_l_u_s_h_n_o_w is _T_R_U_E, then the stream's _w_r_i_t_e_p_r_o_c will be called; otherwise, _w_r_i_t_e_p_r_o_c will be called when the output buffer has been filled. The routine _x_d_r_r_e_c___s_k_i_p_r_e_c_o_r_d_(_) causes an input stream's position to be moved past the current record boundary and onto the beginning of the next record in the stream. If there is no more data in the stream's input buffer, then the routine _x_d_r_r_e_c___e_o_f_(_) returns _T_R_U_E. That is not to say that there is no more data in the underlying file descrip- tor. 44..99.. XXDDRR SSttrreeaamm IImmpplleemmeennttaattiioonn This section provides the abstract data types needed to implement new instances of XDR streams. 44..99..11.. TThhee XXDDRR OObbjjeecctt The following structure defines the interface to an XDR stream: External Data Representation: Sun Technical Notes Page 23 enum xdr_op { XDR_ENCODE=0, XDR_DECODE=1, XDR_FREE=2 }; typedef struct { enum xdr_op x_op; /* _o_p_e_r_a_t_i_o_n_; _f_a_s_t _a_d_d_e_d _p_a_r_a_m */ struct xdr_ops { bool_t (*x_getlong)(); /* _g_e_t _l_o_n_g _f_r_o_m _s_t_r_e_a_m */ bool_t (*x_putlong)(); /* _p_u_t _l_o_n_g _t_o _s_t_r_e_a_m */ bool_t (*x_getbytes)(); /* _g_e_t _b_y_t_e_s _f_r_o_m _s_t_r_e_a_m */ bool_t (*x_putbytes)(); /* _p_u_t _b_y_t_e_s _t_o _s_t_r_e_a_m */ u_int (*x_getpostn)(); /* _r_e_t_u_r_n _s_t_r_e_a_m _o_f_f_s_e_t */ bool_t (*x_setpostn)(); /* _r_e_p_o_s_i_t_i_o_n _o_f_f_s_e_t */ caddr_t (*x_inline)(); /* _p_t_r _t_o _b_u_f_f_e_r_e_d _d_a_t_a */ VOID (*x_destroy)(); /* _f_r_e_e _p_r_i_v_a_t_e _a_r_e_a */ } *x_ops; caddr_t x_public; /* _u_s_e_r_s_' _d_a_t_a */ caddr_t x_private; /* _p_o_i_n_t_e_r _t_o _p_r_i_v_a_t_e _d_a_t_a */ caddr_t x_base; /* _p_r_i_v_a_t_e _f_o_r _p_o_s_i_t_i_o_n _i_n_f_o */ int x_handy; /* _e_x_t_r_a _p_r_i_v_a_t_e _w_o_r_d */ } XDR; The _x___o_p field is the current operation being performed on the stream. This field is important to the XDR primitives, but should not affect a stream's implementation. That is, a stream's implementation should not depend on this value. The fields _x___p_r_i_v_a_t_e, _x___b_a_s_e, and _x___h_a_n_d_y are private to the particular stream's implementation. The field _x___p_u_b_l_i_c is for the XDR client and should never be used by the XDR stream implementations or the XDR primitives. _x___g_e_t_p_o_s_t_n_(_), _x___s_e_t_p_o_s_t_n_(_) and _x___d_e_s_t_r_o_y_(_) are macros for accessing opera- tions. The operation _x___i_n_l_i_n_e_(_) takes two parameters: an XDR *, and an unsigned integer, which is a byte count. The routine returns a pointer to a piece of the stream's inter- nal buffer. The caller can then use the buffer segment for any purpose. From the stream's point of view, the bytes in the buffer segment have been consumed or put. The routine may return _N_U_L_L if it cannot return a buffer segment of the requested size. (The _x___i_n_l_i_n_e_(_) routine is for cycle squeezers. Use of the resulting buffer is not data- portable. Users are encouraged not to use this feature.) The operations _x___g_e_t_b_y_t_e_s_(_) and _x___p_u_t_b_y_t_e_s_(_) blindly get and put sequences of bytes from or to the underlying stream; they return _T_R_U_E if they are successful, and _F_A_L_S_E other- wise. The routines have identical parameters (replace _x_x_x): bool_t xxxbytes(xdrs, buf, bytecount) XDR *xdrs; char *buf; u_int bytecount; The operations _x___g_e_t_l_o_n_g_(_) and _x___p_u_t_l_o_n_g_(_) receive and put long numbers from and to the data stream. It is the respon- sibility of these routines to translate the numbers between the machine representation and the (standard) external Page 24 External Data Representation: Sun Technical Notes representation. The UNIX primitives _h_t_o_n_l_(_) and _n_t_o_h_l_(_) can be helpful in accomplishing this. The higher-level XDR implementation assumes that signed and unsigned long inte- gers contain the same number of bits, and that nonnegative integers have the same bit representations as unsigned inte- gers. The routines return _T_R_U_E if they succeed, and _F_A_L_S_E otherwise. They have identical parameters: bool_t xxxlong(xdrs, lp) XDR *xdrs; long *lp; Implementors of new XDR streams must make an XDR structure (with new operation routines) available to clients, using some kind of create routine. 55.. AAddvvaanncceedd TTooppiiccss This section describes techniques for passing data struc- tures that are not covered in the preceding sections. Such structures include linked lists (of arbitrary lengths). Unlike the simpler examples covered in the earlier sections, the following examples are written using both the XDR C library routines and the XDR data description language. The _E_x_t_e_r_n_a_l _D_a_t_a _R_e_p_r_e_s_e_n_t_a_t_i_o_n _S_t_a_n_d_a_r_d_: _P_r_o_t_o_c_o_l _S_p_e_c_i_f_i_c_a_- _t_i_o_n describes this language in complete detail. 55..11.. LLiinnkkeedd LLiissttss The last example in the _P_o_i_n_t_e_r_s topic earlier in this chap- ter presented a C data structure and its associated XDR rou- tines for a individual's gross assets and liabilities. The example is duplicated below: struct gnumbers { long g_assets; long g_liabilities; }; bool_t xdr_gnumbers(xdrs, gp) XDR *xdrs; struct gnumbers *gp; { if (xdr_long(xdrs, &(gp->g_assets))) return(xdr_long(xdrs, &(gp->g_liabilities))); return(FALSE); } Now assume that we wish to implement a linked list of such information. A data structure could be constructed as fol- lows: External Data Representation: Sun Technical Notes Page 25 struct gnumbers_node { struct gnumbers gn_numbers; struct gnumbers_node *gn_next; }; typedef struct gnumbers_node *gnumbers_list; The head of the linked list can be thought of as the data object; that is, the head is not merely a convenient short- hand for a structure. Similarly the _g_n___n_e_x_t field is used to indicate whether or not the object has terminated. Unfortunately, if the object continues, the _g_n___n_e_x_t field is also the address of where it continues. The link addresses carry no useful information when the object is serialized. The XDR data description of this linked list is described by the recursive declaration of _g_n_u_m_b_e_r_s___l_i_s_t: struct gnumbers { int g_assets; int g_liabilities; }; struct gnumbers_node { gnumbers gn_numbers; gnumbers_node *gn_next; }; In this description, the boolean indicates whether there is more data following it. If the boolean is _F_A_L_S_E, then it is the last data field of the structure. If it is _T_R_U_E, then it is followed by a gnumbers structure and (recursively) by a _g_n_u_m_b_e_r_s___l_i_s_t. Note that the C declaration has no boolean explicitly declared in it (though the _g_n___n_e_x_t field implic- itly carries the information), while the XDR data descrip- tion has no pointer explicitly declared in it. Hints for writing the XDR routines for a _g_n_u_m_b_e_r_s___l_i_s_t fol- low easily from the XDR description above. Note how the primitive _x_d_r___p_o_i_n_t_e_r_(_) is used to implement the XDR union above. Page 26 External Data Representation: Sun Technical Notes bool_t xdr_gnumbers_node(xdrs, gn) XDR *xdrs; gnumbers_node *gn; { return(xdr_gnumbers(xdrs, &gn->gn_numbers) && xdr_gnumbers_list(xdrs, &gp->gn_next)); } bool_t xdr_gnumbers_list(xdrs, gnp) XDR *xdrs; gnumbers_list *gnp; { return(xdr_pointer(xdrs, gnp, sizeof(struct gnumbers_node), xdr_gnumbers_node)); } The unfortunate side effect of XDR'ing a list with these routines is that the C stack grows linearly with respect to the number of node in the list. This is due to the recur- sion. The following routine collapses the above two mutually recursive into a single, non-recursive one. External Data Representation: Sun Technical Notes Page 27 bool_t xdr_gnumbers_list(xdrs, gnp) XDR *xdrs; gnumbers_list *gnp; { bool_t more_data; gnumbers_list *nextp; for (;;) { more_data = (*gnp != NULL); if (!xdr_bool(xdrs, &more_data)) { return(FALSE); } if (! more_data) { break; } if (xdrs->x_op == XDR_FREE) { nextp = &(*gnp)->gn_next; } if (!xdr_reference(xdrs, gnp, sizeof(struct gnumbers_node), xdr_gnumbers)) { return(FALSE); } gnp = (xdrs->x_op == XDR_FREE) ? nextp : &(*gnp)->gn_next; } *gnp = NULL; return(TRUE); } The first task is to find out whether there is more data or not, so that this boolean information can be serialized. Notice that this statement is unnecessary in the _X_D_R___D_E_C_O_D_E case, since the value of more_data is not known until we deserialize it in the next statement. The next statement XDR's the more_data field of the XDR union. Then if there is truly no more data, we set this last pointer to _N_U_L_L to indicate the end of the list, and return _T_R_U_E because we are done. Note that setting the pointer to _N_U_L_L is only important in the _X_D_R___D_E_C_O_D_E case, since it is already _N_U_L_L in the _X_D_R___E_N_C_O_D_E and XDR_FREE cases. Next, if the direction is _X_D_R___F_R_E_E, the value of _n_e_x_t_p is set to indicate the location of the next pointer in the list. We do this now because we need to dereference gnp to find the location of the next item in the list, and after the next statement the storage pointed to by _g_n_p will be freed up and no be longer valid. We can't do this for all directions though, because in the _X_D_R___D_E_C_O_D_E direction the value of _g_n_p won't be set until the next statement. Page 28 External Data Representation: Sun Technical Notes Next, we XDR the data in the node using the primitive _x_d_r___r_e_f_e_r_e_n_c_e(). _x_d_r___r_e_f_e_r_e_n_c_e_(_) is like _x_d_r___p_o_i_n_t_e_r_(_) which we used before, but it does not send over the boolean indicating whether there is more data. We use it instead of _x_d_r___p_o_i_n_t_e_r_(_) because we have already XDR'd this information ourselves. Notice that the xdr routine passed is not the same type as an element in the list. The routine passed is _x_d_r___g_n_u_m_b_e_r_s(), for XDR'ing gnumbers, but each element in the list is actually of type _g_n_u_m_b_e_r_s___n_o_d_e. We don't pass _x_d_r___g_n_u_m_b_e_r_s___n_o_d_e_(_) because it is recursive, and instead use _x_d_r___g_n_u_m_b_e_r_s_(_) which XDR's all of the non-recursive part. Note that this trick will work only if the _g_n___n_u_m_b_e_r_s field is the first item in each element, so that their addresses are identical when passed to _x_d_r___r_e_f_e_r_e_n_c_e(). Finally, we update _g_n_p to point to the next item in the list. If the direction is _X_D_R___F_R_E_E, we set it to the previ- ously saved value, otherwise we can dereference _g_n_p to get the proper value. Though harder to understand than the recursive version, this non-recursive routine is far less likely to blow the C stack. It will also run more effi- ciently since a lot of procedure call overhead has been removed. Most lists are small though (in the hundreds of items or less) and the recursive version should be suffi- cient for them.