The XDR functions used to encode function arguments and results must know how to encode the data for any API version. This is important both so that all the data gets correctly transmitted and so that protocol compatibility between clients or servers using the new library but an old API version is maintained; specific, new kadmind servers should support old kadm5 clients.
The signature of all XDR functions is strictly defined: they take the address of an XDR function and the address of the data object to be encoded or decoded. It is thus impossible to provide the API version of the data object as an additional argument to an XDR function. There are two other means to convey the information, storing the API version to use as a field in the data object itself and creating separate XDR functions to handle each different version of the data object, and both of them are used in KADM5.
In the client library, each kadm5 function collects its arguments into a single structure to be passed by the RPC; similarly, it expects all of the results to come back as a single structure from the RPC that it will then decode back into its constituent pieces (these are the standard ONC RPC semantics). In order to pass versioning information to the XDR functions, each function argument and result datatype has a filed to store the API version. For example, consider kadm5_get_principal's structures:
struct gprinc_arg { krb5_ui_4 api_version; krb5_principal princ; long mask; }; typedef struct gprinc_arg gprinc_arg; bool_t xdr_gprinc_arg(); struct gprinc_ret { krb5_ui_4 api_version; kadm5_ret_t code; kadm5_principal_ent_rec rec; }; typedef struct gprinc_ret gprinc_ret; bool_t xdr_gprinc_ret();kadm5_get_principal (in client_principal.c) assigns the api_version field of the gprinc_arg to the version specified by its caller, assigns the princ field based on its arguments, and assigns the mask field from its argument if the caller specified VERSION_2. It then calls the RPC function clnt_call, specifying the XDR functions xdr_gprinc_arg and xdr_gprinc_ret to handle the arguments and results.
xdr_gprinc_arg is invoked with a pointer to the gprinc_arg structure just described. It first encodes the api_version field; this allows the server to know what to expect. It then encodes the krb5_principal structure and, if api_version is VERSION_2, the mask. If api_version is not VERSION_2, it does not encode anything in place of the mask, because an old VERSION_1 server will not expect any other data to arrive on the wire there.
The server performs the kadm5_get_principal call and returns its results in an XDR encoded gprinc_ret structure. clnt_call, which has been blocking until the results arrived, invokes xdr_gprinc_ret with a pointer to the encoded data for it to decode. xdr_gprinc_ret first decodes the api_version field, and then the code field since that is present in all versions to date. The kadm5_principal_ent_rec presents a problem, however. The structure does not itself contain an api_version field, but the structure is different between the two versions. Thus, a single XDR function cannot decode both versions of the structure because it will have no way to decide which version to expect. The solution is to have two functions, kadm5_principal_ent_rec_v1 and kadm5_principal_ent_rec, which always decode according to VERSION_1 or VERSION_2, respectively. gprinc_ret knows which one to invoke because it has the api_version field returned by the server (which is always the same as that specified by the client in the gpring_arg).
In hindsight, it probably would have been better to encode the API version of all structures directly in a version field in the structure itself; then multiple XDR functions for a single data type wouldn't be necessary, and the data objects would stand complete on their own. This can be added in a future API version if desired.