/* Copyright (c) 2010, 2011
 * The Regents of the University of Michigan
 * All Rights Reserved
 * 
 * Olga Kornievskaia <aglo@umich.edu>
 * Casey Bodley <cbodley@umich.edu>
 *
 * Permission is granted to use, copy and redistribute this software
 * for noncommercial education and research purposes, so long as no
 * fee is charged, and so long as the name of the University of Michigan
 * is not used in any advertising or publicity pertaining to the use
 * or distribution of this software without specific, written prior
 * authorization.  Permission to modify or otherwise create derivative
 * works of this software is not granted.
 *
 * This software is provided as is, without representation or warranty
 * of any kind either express or implied, including without limitation
 * the implied warranties of merchantability, fitness for a particular
 * purpose, or noninfringement.  The Regents of the University of
 * Michigan shall not be liable for any damages, including special,
 * indirect, incidental, or consequential damages, with respect to any
 * claim arising out of or in connection with the use of the software,
 * even if it has been or is hereafter advised of the possibility of
 * such damages.
 */

#include <Windows.h>
#include <strsafe.h>

#include "nfs41_compound.h"
#include "nfs41_ops.h"
#include "nfs41_xdr.h"
#include "util.h"
#include "daemon_debug.h"
#include "rpc/rpc.h"

static bool_t encode_file_attrs(
    fattr4 *attrs,
    nfs41_file_info *info);

static __inline int unexpected_op(uint32_t op, uint32_t expected)
{
    if (op == expected)
        return 0;

    eprintf("Op table mismatch. Got %s (%d), expected %s (%d).\n",
        nfs_opnum_to_string(op), op,
        nfs_opnum_to_string(expected), expected);
    return 1;
}

/* typedef uint32_t bitmap4<> */
bool_t xdr_bitmap4(
    XDR *xdr,
    bitmap4 *bitmap)
{
    uint32_t i;

    if (xdr->x_op == XDR_ENCODE) {
        if (bitmap->count > 3) {
            eprintf("encode_bitmap4: count (%d) must be <= 3\n",
                bitmap->count);
            return FALSE;
        }
        if (!xdr_u_int32_t(xdr, &bitmap->count))
            return FALSE;

        for (i = 0; i < bitmap->count; i++)
            if (!xdr_u_int32_t(xdr, &bitmap->arr[i]))
                return FALSE;

    } else if (xdr->x_op == XDR_DECODE) {
        if (!xdr_u_int32_t(xdr, &bitmap->count))
            return FALSE;
        if (bitmap->count > 3) {
            eprintf("decode_bitmap4: count (%d) must be <= 3\n",
                bitmap->count);
            return FALSE;
        }

        for (i = 0; i < bitmap->count; i++)
            if (!xdr_u_int32_t(xdr, &bitmap->arr[i]))
                return FALSE;
    } else 
        return FALSE;

    return TRUE;
}

/* nfstime4 */
static bool_t xdr_nfstime4(
    XDR *xdr,
    nfstime4 *nt)
{
    if (!xdr_hyper(xdr, &nt->seconds))
        return FALSE;

    return xdr_u_int32_t(xdr, &nt->nseconds);
}


/* settime4 */
static uint32_t settime_how(
    nfstime4 *newtime,
    const nfstime4 *time_delta)
{
    nfstime4 current;
    get_nfs_time(&current);
    /* get the absolute difference between current and newtime */
    nfstime_diff(&current, newtime, &current);
    nfstime_abs(&current, &current);
    /* compare the difference with time_delta */
    nfstime_diff(time_delta, &current, &current);
    /* use client time if diff > delta (i.e. time_delta - current < 0) */
    return current.seconds < 0 ? SET_TO_CLIENT_TIME4 : SET_TO_SERVER_TIME4;
}

static bool_t xdr_settime4(
    XDR *xdr,
    nfstime4 *nt,
    const nfstime4 *time_delta)
{
    uint32_t how = settime_how(nt, time_delta);

    if (xdr->x_op != XDR_ENCODE) /* not used for decode */
        return FALSE;

    if (!xdr_u_int32_t(xdr, &how))
        return FALSE;

    if (how == SET_TO_CLIENT_TIME4)
        return xdr_nfstime4(xdr, nt);

    return TRUE;
}

/* stateid4 */
static bool_t xdr_stateid4(
    XDR *xdr,
    stateid4 *si)
{
    if (!xdr_u_int32_t(xdr, &si->seqid))
        return FALSE;

    return xdr_opaque(xdr, (char *)si->other, NFS4_STATEID_OTHER);
}

/* fattr4 */
bool_t xdr_fattr4(
    XDR *xdr,
    fattr4 *fattr)
{
    unsigned char *attr_vals = fattr->attr_vals;

    if (!xdr_bitmap4(xdr, &fattr->attrmask))
        return FALSE;

    return xdr_bytes(xdr, (char **)&attr_vals, &fattr->attr_vals_len, NFS4_OPAQUE_LIMIT);
}

/* nfs41_fh */
static bool_t xdr_fh(
    XDR *xdr,
    nfs41_fh *fh)
{
    unsigned char *pfh = fh->fh;
    return xdr_bytes(xdr, (char **)&pfh, &fh->len, NFS4_FHSIZE);
}

/* nfs41_fsid */
static bool_t xdr_fsid(
    XDR *xdr,
    nfs41_fsid *fsid)
{
    if (!xdr_u_hyper(xdr, &fsid->major))
        return FALSE;

    return xdr_u_hyper(xdr, &fsid->minor);
}


/* nfs41_component */
static bool_t encode_component(
    XDR *xdr,
    const nfs41_component *component)
{
    uint32_t len = component->len;
    return xdr_bytes(xdr, (char **)&component->name, &len, NFS4_OPAQUE_LIMIT);
}

static bool_t decode_component(
    XDR *xdr,
    nfs41_component *component)
{
    bool_t result;
    uint32_t len;

    result = xdr_bytes(xdr, (char **)&component->name, &len, NFS4_OPAQUE_LIMIT);
    component->len = (result == FALSE) ? 0 : (unsigned short)len;
    return result;
}


/* state_owner4 */
static bool_t xdr_state_owner4(
    XDR *xdr,
    state_owner4 *so)
{
    u_quad_t clientid = 0;
    unsigned char *owner = so->owner;

    /* 18.16.3. "The client can set the clientid field to any value and
     * the server MUST ignore it.  Instead the server MUST derive the
     * client ID from the session ID of the SEQUENCE operation of the
     * COMPOUND request. */
    if (xdr->x_op == XDR_ENCODE) {
        if (!xdr_u_hyper(xdr, &clientid)) /* clientid = 0 */
            return FALSE;
    } else if (xdr->x_op == XDR_DECODE) {
        if (!xdr_u_hyper(xdr, &clientid))
            return FALSE;
    } else return FALSE;

    return xdr_bytes(xdr, (char **)&owner, &so->owner_len, NFS4_OPAQUE_LIMIT);
}

static bool_t xdr_layout_types(
    XDR *xdr,
    uint32_t *layout_type)
{
    u_int32_t i, count, type;

    if (xdr->x_op != XDR_DECODE) {
        eprintf("xdr_layout_types: xdr->x_op is not XDR_DECODE! "
            "x_op %d not supported.\n", xdr->x_op);
        return FALSE;
    }

    *layout_type = 0;

    if (!xdr_u_int32_t(xdr, &count))
        return FALSE;

    for (i = 0; i < count; i++) {
        if (!xdr_u_int32_t(xdr, &type))
            return FALSE;

        *layout_type |= 1 << (type - 1);
    }
    return TRUE;
}

static bool_t xdr_threshold_item(
    XDR *xdr,
    threshold_item4 *item)
{
    bitmap4 bitmap;

    if (!xdr_u_int32_t(xdr, &item->type))
        return FALSE;

    if (!xdr_bitmap4(xdr, &bitmap))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &bitmap.count))
        return FALSE;

    if (bitmap.count) {
        if (bitmap.arr[0] & 0x1 && !xdr_u_hyper(xdr, &item->hints[0]))
            return FALSE;
        if (bitmap.arr[0] & 0x2 && !xdr_u_hyper(xdr, &item->hints[1]))
            return FALSE;
        if (bitmap.arr[0] & 0x4 && !xdr_u_hyper(xdr, &item->hints[2]))
            return FALSE;
        if (bitmap.arr[0] & 0x8 && !xdr_u_hyper(xdr, &item->hints[3]))
            return FALSE;
    }
    return TRUE;
}

static bool_t xdr_mdsthreshold(
    XDR *xdr,
    mdsthreshold4 *mdsthreshold)
{
    uint32_t i;

    if (!xdr_u_int32_t(xdr, &mdsthreshold->count))
        return FALSE;

    if (mdsthreshold->count > MAX_MDSTHRESHOLD_ITEMS)
        return FALSE;

    for (i = 0; i < mdsthreshold->count; i++)
        if (!xdr_threshold_item(xdr, &mdsthreshold->items[i]))
            return FALSE;
    return TRUE;
}

static bool_t xdr_nfsace4(
    XDR *xdr,
    nfsace4 *ace)
{
    char *who = ace->who;

    if (!xdr_u_int32_t(xdr, &ace->acetype))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &ace->aceflag))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &ace->acemask))
        return FALSE;

    /* 'who' is a static array, so don't try to free it */
    if (xdr->x_op == XDR_FREE)
        return TRUE;

    return xdr_string(xdr, &who, NFS4_OPAQUE_LIMIT);
}

static bool_t xdr_nfsdacl41(
    XDR *xdr,
    nfsacl41 *acl)
{
    if (!xdr_u_int32_t(xdr, &acl->flag))
        return FALSE;

    return xdr_array(xdr, (char**)&acl->aces, &acl->count,
        32, sizeof(nfsace4), (xdrproc_t)xdr_nfsace4);
}

static bool_t xdr_nfsacl41(
    XDR *xdr,
    nfsacl41 *acl)
{
    return xdr_array(xdr, (char**)&acl->aces, &acl->count,
        32, sizeof(nfsace4), (xdrproc_t)xdr_nfsace4);
}

void nfsacl41_free(nfsacl41 *acl)
{
    XDR xdr = { XDR_FREE };
    xdr_nfsacl41(&xdr, acl);
}

/* pathname4
 * decode a variable array of components into a nfs41_abs_path */
static bool_t decode_pathname4(
    XDR *xdr,
    nfs41_abs_path *path)
{
    char *pos;
    u_int32_t i, count, len, remaining;

    /* decode the number of components */
    if (!xdr_u_int32_t(xdr, &count))
        return FALSE;

    pos = (char *)path->path;
    remaining = NFS41_MAX_PATH_LEN;

    /* decode each component */
    for (i = 0; i < count; i++) {
        len = remaining;
        if (!xdr_bytes(xdr, (char **)&pos, &len, NFS41_MAX_PATH_LEN))
            return FALSE;
        remaining -= len;
        pos += len;

        if (i < count-1) { /* add a \ between components */
            if (remaining < 1)
                return FALSE;
            *pos++ = '\\';
            remaining--;
        }
    }
    path->len = (unsigned short)(NFS41_MAX_PATH_LEN - remaining);
    return TRUE;
}

/* fs_location4 */
static bool_t decode_fs_location4(
    XDR *xdr,
    fs_location4 *location)
{
    fs_location_server *arr;
    char *address;
    u_int32_t i, count, len;

    /* decode the number of servers */
    if (!xdr_u_int32_t(xdr, &count))
        return FALSE;

    /* allocate the fs_location_server array */
    if (count == 0) {
        free(location->servers);
        arr = NULL;
    } else if (count != location->server_count) {
        arr = realloc(location->servers, count * sizeof(fs_location_server));
        if (arr == NULL)
            return FALSE;
        ZeroMemory(arr, count * sizeof(fs_location_server));
    } else {
        arr = location->servers;
    }

    location->servers = arr;
    location->server_count = count;

    for (i = 0; i < count; i++) {
        len = NFS41_HOSTNAME_LEN;
        address = arr[i].address;
        if (!xdr_bytes(xdr, &address, &len, NFS41_HOSTNAME_LEN)) {
            free(arr);
            return FALSE;
        }
        arr[i].address[len] = '\0';
    }

    return decode_pathname4(xdr, &location->path);
}

/* fs_locations4 */
static bool_t decode_fs_locations4(
    XDR *xdr,
    fs_locations4 *locations)
{
    u_int32_t i, count;
    fs_location4 *arr;

    if (!decode_pathname4(xdr, &locations->path))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &count))
        return FALSE;

    /* allocate the fs_location array */
    if (count == 0) {
        free(locations->locations);
        arr = NULL;
    } else if (count != locations->location_count) {
        arr = realloc(locations->locations, count * sizeof(fs_location4));
        if (arr == NULL)
            return FALSE;
        ZeroMemory(arr, count * sizeof(fs_location4));
    } else {
        arr = locations->locations;
    }

    locations->locations = arr;
    locations->location_count = count;

    for (i = 0; i < count; i++) {
        if (!decode_fs_location4(xdr, &arr[i])) {
            free(arr);
            return FALSE;
        }
    }
    return TRUE;
}

/*
 * OP_EXCHANGE_ID
 */
static bool_t xdr_client_owner4(
    XDR *xdr,
    client_owner4 *co)
{
    unsigned char *co_ownerid = co->co_ownerid;
    if (!xdr_opaque(xdr, (char *)&co->co_verifier[0], NFS4_VERIFIER_SIZE))
        return FALSE;

    return xdr_bytes(xdr, (char **)&co_ownerid, &co->co_ownerid_len, NFS4_OPAQUE_LIMIT);
}

#if 0
static bool_t encode_state_protect_ops4(
    XDR *xdr,
    state_protect_ops4 *spo)
{
    if (!xdr_bitmap4(xdr, &spo->spo_must_enforce))
        return FALSE;

    return xdr_bitmap4(xdr, &spo->spo_must_allow);
}

static bool_t encode_ssv_sp_parms4(
    XDR *xdr,
    ssv_sp_parms4 *spp)
{
    if (!encode_state_protect_ops4(xdr, &spp->ssp_ops))
        return FALSE;

    if (!xdr_bytes(xdr, &spp->ssp_hash_algs,
        &spp->ssp_hash_algs_len, NFS4_OPAQUE_LIMIT))
        return FALSE;

    if (!xdr_bytes(xdr, &spp->ssp_encr_algs,
        &spp->ssp_encr_algs_len, NFS4_OPAQUE_LIMIT))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &spp->ssp_window))
        return FALSE;

    return xdr_u_int32_t(xdr, &spp->ssp_num_gss_handles);
}
#endif

static bool_t xdr_state_protect4_a(
    XDR *xdr,
    state_protect4_a *spa)
{
    bool_t result = TRUE;

    if (!xdr_u_int32_t(xdr, (u_int32_t *)&spa->spa_how))
        return FALSE;

    switch (spa->spa_how)
    {
    case SP4_NONE:
        break;
#if 0
    case SP4_MACH_CRED:
        result = xdr_state_protect_ops4(xdr, &spa->u.spa_mach_ops);
        break;
    case SP4_SSV:
        result = xdr_ssv_sp_parms4(xdr, &spa->u.spa_ssv_parms);
        break;
#endif
    default:
        eprintf("encode_state_protect4_a: state protect "
            "type %d not supported.\n", spa->spa_how);
        result = FALSE;
        break;
    }
    return result;
}

static bool_t xdr_nfs_impl_id4(
    XDR *xdr,
    nfs_impl_id4 *nii)
{
    unsigned char *nii_domain = nii->nii_domain;
    unsigned char *nii_name = nii->nii_name;

    if (!xdr_bytes(xdr, (char **)&nii_domain, &nii->nii_domain_len, NFS4_OPAQUE_LIMIT))
        return FALSE;

    if (!xdr_bytes(xdr, (char **)&nii_name, &nii->nii_name_len, NFS4_OPAQUE_LIMIT))
        return FALSE;

    return xdr_nfstime4(xdr, &nii->nii_date);
}


static bool_t encode_op_exchange_id(
    XDR *xdr,
    nfs_argop4 *argop)
{
    uint32_t zero = 0;
    uint32_t one = 1;

    nfs41_exchange_id_args *args = (nfs41_exchange_id_args*)argop->arg;

    if (unexpected_op(argop->op, OP_EXCHANGE_ID))
        return FALSE;

    if (!xdr_client_owner4(xdr, args->eia_clientowner))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &args->eia_flags))
        return FALSE;

    if (!xdr_state_protect4_a(xdr, &args->eia_state_protect))
        return FALSE;

    if (args->eia_client_impl_id)
    {
        if (!xdr_u_int32_t(xdr, &one))
            return FALSE;
        return xdr_nfs_impl_id4(xdr, args->eia_client_impl_id);
    }
    else
        return xdr_u_int32_t(xdr, &zero);
}

#if 0

static bool_t decode_state_protect_ops4(
    XDR *xdr,
    state_protect_ops4 *spo)
{
    if (!xdr_bitmap4(xdr, &spo->spo_must_enforce))
        return FALSE;

    return xdr_bitmap4(xdr, &spo->spo_must_allow);
}

static bool_t decode_ssv_prot_info4(
    XDR *xdr,
    ssv_prot_info4 *spi)
{
/*  uint32_t i; */

    if (!decode_state_protect_ops4(xdr, &spi->spi_ops))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &spi->spi_hash_alg))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &spi->spi_encr_alg))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &spi->spi_ssv_len))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &spi->spi_window))
        return FALSE;

/* TODO: spi->spi_handles */
    return xdr_u_int32_t(xdr, 0);
    /*
    if (!xdr_u_int32_t(xdr, &spi->spi_handles.count))
        return FALSE;

    for (i = 0; i < spi->spi_handles.count; i++)
        if (!xdr_opaque(xdr, &spi->spi_handles.arr[i])
            return FALSE;
*/
    return TRUE;
}
#endif

static bool_t xdr_state_protect4_r(
    XDR *xdr,
    state_protect4_r *spr)
{
    bool_t result = TRUE;

    if (!xdr_u_int32_t(xdr, (uint32_t *)&spr->spr_how))
        return FALSE;

    switch (spr->spr_how)
    {
    case SP4_NONE:
        break;
#if 0
    case SP4_MACH_CRED:
        result = decode_state_protect_ops4(xdr, &spr->u.spr_mach_ops);
        break;
    case SP4_SSV:
        result = decode_ssv_prot_info4(xdr, &spr->u.spr_ssv_info);
        break;
#endif
    default:
        eprintf("decode_state_protect4_r: state protect "
            "type %d not supported.\n", spr->spr_how);
        result = FALSE;
        break;
    }
    return result;
}

static bool_t xdr_server_owner4(
    XDR *xdr,
    server_owner4 *so)
{
    char *so_major_id = so->so_major_id;

    if (!xdr_u_hyper(xdr, &so->so_minor_id))
        return FALSE;

    return xdr_bytes(xdr, (char **)&so_major_id,
        &so->so_major_id_len, NFS4_OPAQUE_LIMIT);
}

static bool_t decode_op_exchange_id(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_exchange_id_res *res = (nfs41_exchange_id_res*)resop->res;
    char *server_scope = (char *)res->server_scope;

    if (unexpected_op(resop->op, OP_EXCHANGE_ID))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (res->status != NFS4_OK)
        return TRUE;

    if (!xdr_u_hyper(xdr, &res->clientid))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->sequenceid))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->flags))
        return FALSE;

    if (!xdr_state_protect4_r(xdr, &res->state_protect))
        return FALSE;

    if (!xdr_server_owner4(xdr, &res->server_owner))
        return FALSE;

    return xdr_bytes(xdr, &server_scope,
        &res->server_scope_len, NFS4_OPAQUE_LIMIT);
}

/*
 * OP_CREATE_SESSION
 */
static bool_t xdr_channel_attrs4(
    XDR *xdr,
    nfs41_channel_attrs *attrs)
{
    uint32_t zero = 0;
    uint32_t one = 1;

    /* count4 ca_headerpadsize */
    if (!xdr_u_int32_t(xdr, &attrs->ca_headerpadsize))
        return FALSE;

    /* count4 ca_maxrequestsize */
    if (!xdr_u_int32_t(xdr, &attrs->ca_maxrequestsize))
        return FALSE;

    /* count4 ca_maxresponsesize */
    if (!xdr_u_int32_t(xdr, &attrs->ca_maxresponsesize))
        return FALSE;

    /* count4 ca_maxresponsesize_cached */
    if (!xdr_u_int32_t(xdr, &attrs->ca_maxresponsesize_cached))
        return FALSE;

    /* count4 ca_maxoperations */
    if (!xdr_u_int32_t(xdr, &attrs->ca_maxoperations))
        return FALSE;

    /* count4 ca_maxrequests */
    if (!xdr_u_int32_t(xdr, &attrs->ca_maxrequests))
        return FALSE;

    if (xdr->x_op == XDR_ENCODE) {
        /* uint32_t ca_rdma_ird<1> */
        if (attrs->ca_rdma_ird)
        {
            if (!xdr_u_int32_t(xdr, &one))
                return FALSE;
            return xdr_u_int32_t(xdr, attrs->ca_rdma_ird);
        }
        else {
            return xdr_u_int32_t(xdr, &zero);
        }
    }
    else if (xdr->x_op == XDR_DECODE) {
#if 0
        u_int32_t count;
        /* uint32_t ca_rdma_ird<1> */
        if (!xdr_u_int32_t(xdr, &count))
            return FALSE;
        if (count > 1)
            return FALSE;
        if (count)
            return xdr_u_int32_t(xdr, attrs->ca_rdma_ird);
        else
#endif
            return TRUE;
    }
    else {
        eprintf("%s: xdr->x_op %d not supported.\n",
            "xdr_channel_attrs4", xdr->x_op);
        return FALSE;
    }
}

static bool_t encode_backchannel_sec_parms(
    XDR *xdr,
    nfs41_callback_secparms *args)
{
    uint32_t zero = 0;

    if (!xdr_u_int32_t(xdr, &args->type))
        return FALSE;

    switch (args->type)  {
    case AUTH_NONE: return TRUE;
    case AUTH_SYS:
        if (!xdr_u_int32_t(xdr, &args->u.auth_sys.stamp))
            return FALSE;
        if (!xdr_string(xdr, &args->u.auth_sys.machinename, NI_MAXHOST))
            return FALSE;
        return xdr_u_int32_t(xdr, &zero) && xdr_u_int32_t(xdr, &zero) && 
                xdr_u_int32_t(xdr, &zero);
    case RPCSEC_GSS:
    default:
        return FALSE;
    }
}

static bool_t encode_op_create_session(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_create_session_args *args = (nfs41_create_session_args*)argop->arg;
    nfs41_callback_secparms *cb_secparams = args->csa_cb_secparams;
    uint32_t cb_count = 2;

    if (unexpected_op(argop->op, OP_CREATE_SESSION))
        return FALSE;

    /* clientid4 csa_clientid */
    if (!xdr_u_hyper(xdr, &args->csa_clientid))
        return FALSE;

    /* sequenceid4 csa_sequence */
    if (!xdr_u_int32_t(xdr, &args->csa_sequence))
        return FALSE;

    /* TODO: uint32_t csa_flags = 0 */
    if (!xdr_u_int32_t(xdr, &args->csa_flags))
        return FALSE;

    /* channel_attrs4 csa_fore_chan_attrs */
    if (!xdr_channel_attrs4(xdr, &args->csa_fore_chan_attrs))
        return FALSE;

    /* channel_attrs4 csa_back_chan_attrs */
    if (!xdr_channel_attrs4(xdr, &args->csa_back_chan_attrs))
        return FALSE;

    /* TODO: uint32_t csa_cb_program = 1234 */
    if (!xdr_u_int32_t(xdr, &args->csa_cb_program))
        return FALSE;

    return xdr_array(xdr, (char **)&cb_secparams, &cb_count,
        3, sizeof(nfs41_callback_secparms), (xdrproc_t) encode_backchannel_sec_parms);
}

static bool_t decode_op_create_session(
    XDR *xdr,
    nfs_resop4 *resop)
{
    uint32_t opstatus;
    nfs41_create_session_res *res = (nfs41_create_session_res*)resop->res;

    if (unexpected_op(resop->op, OP_CREATE_SESSION))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &opstatus))
        return FALSE;

    if (opstatus != NFS4_OK)
        return TRUE;

    if (!xdr_opaque(xdr, (char *)res->csr_sessionid, NFS4_SESSIONID_SIZE))
        return FALSE;

    /* sequenceid4 csr_sequence */
    if (!xdr_u_int32_t(xdr, &res->csr_sequence))
        return FALSE;

    /* uint32_t csr_flags */
    if (!xdr_u_int32_t(xdr, &res->csr_flags))
        return FALSE;

    /* channel_attrs4 csr_fore_chan_attrs */
    if (!xdr_channel_attrs4(xdr, res->csr_fore_chan_attrs))
        return FALSE;

    /* channel_attrs4 csr_back_chan_attrs */
    return xdr_channel_attrs4(xdr, res->csr_back_chan_attrs);
}


/*
 * OP_BIND_CONN_TO_SESSION
 */
static bool_t encode_op_bind_conn_to_session(
    XDR *xdr,
    nfs_argop4 *argop)
{
    uint32_t zero = 0;

    nfs41_bind_conn_to_session_args *args =
        (nfs41_bind_conn_to_session_args*)argop->arg;

    if (unexpected_op(argop->op, OP_BIND_CONN_TO_SESSION))
        return FALSE;

    if (!xdr_opaque(xdr, (char *)args->sessionid, NFS4_SESSIONID_SIZE))
        return FALSE;

    if (!xdr_enum(xdr, (enum_t *)&args->dir))
        return FALSE;

    return xdr_u_int32_t(xdr, &zero); /* bctsa_use_conn_in_rdma_mode = false */
}

static bool_t decode_op_bind_conn_to_session(
    XDR *xdr,
    nfs_resop4 *resop)
{
    unsigned char sessionid_ignored[NFS4_SESSIONID_SIZE];
    nfs41_bind_conn_to_session_res *res =
        (nfs41_bind_conn_to_session_res*)resop->res;
    bool_t use_rdma_ignored;

    if (unexpected_op(resop->op, OP_BIND_CONN_TO_SESSION))
        return FALSE;

    if (!xdr_enum(xdr, (enum_t *)&res->status))
        return FALSE;

    if (res->status == NFS4_OK) {
        if (!xdr_opaque(xdr, (char *)&sessionid_ignored, NFS4_SESSIONID_SIZE))
            return FALSE;

        if (!xdr_enum(xdr, (enum_t *)&res->dir))
            return FALSE;

        return xdr_bool(xdr, &use_rdma_ignored);
    }
    return TRUE;
}


/*
 * OP_DESTROY_SESSION
 */
static bool_t encode_op_destroy_session(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_destroy_session_args *args = (nfs41_destroy_session_args*)argop->arg;

    if (unexpected_op(argop->op, OP_DESTROY_SESSION))
        return FALSE;

    return xdr_opaque(xdr, (char *)args->dsa_sessionid, NFS4_SESSIONID_SIZE);
}

static bool_t decode_op_destroy_session(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_destroy_session_res *res = (nfs41_destroy_session_res*)resop->res;

    if (unexpected_op(resop->op, OP_DESTROY_SESSION))
        return FALSE;

    return xdr_u_int32_t(xdr, &res->dsr_status);
}

/*
 * OP_DESTROY_CLIENTID
 */
static bool_t encode_op_destroy_clientid(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_destroy_clientid_args *args = (nfs41_destroy_clientid_args*)argop->arg;

    if (unexpected_op(argop->op, OP_DESTROY_CLIENTID))
        return FALSE;

    return xdr_u_hyper(xdr, &args->dca_clientid);
}

static bool_t decode_op_destroy_clientid(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_destroy_clientid_res *res = (nfs41_destroy_clientid_res*)resop->res;

    if (unexpected_op(resop->op, OP_DESTROY_CLIENTID))
        return FALSE;

    return xdr_u_int32_t(xdr, &res->dcr_status);
}


/*
 * OP_SEQUENCE
 */
static bool_t encode_op_sequence(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_sequence_args *args = (nfs41_sequence_args*)argop->arg;

    if (unexpected_op(argop->op, OP_SEQUENCE))
        return FALSE;

    if (!xdr_opaque(xdr, (char *)args->sa_sessionid, NFS4_SESSIONID_SIZE))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &args->sa_sequenceid))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &args->sa_slotid))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &args->sa_highest_slotid))
        return FALSE;

    return xdr_bool(xdr, &args->sa_cachethis);
}

static bool_t xdr_sequence_res_ok(
    XDR *xdr,
    nfs41_sequence_res_ok *res)
{
    if (!xdr_opaque(xdr, (char *)res->sr_sessionid, NFS4_SESSIONID_SIZE))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->sr_sequenceid))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->sr_slotid))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->sr_highest_slotid))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->sr_target_highest_slotid))
        return FALSE;

    return xdr_u_int32_t(xdr, &res->sr_status_flags);
}

static bool_t decode_op_sequence(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_sequence_res *res = (nfs41_sequence_res*)resop->res;

    if (unexpected_op(resop->op, OP_SEQUENCE))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->sr_status))
        return FALSE;

    if (res->sr_status == NFS4_OK)
        return xdr_sequence_res_ok(xdr, &res->sr_resok4);

    return TRUE;
}


/*
 * OP_RECLAIM_COMPLETE
 */
static bool_t encode_op_reclaim_complete(
    XDR *xdr,
    nfs_argop4 *argop)
{
    bool_t zero = FALSE;

    if (unexpected_op(argop->op, OP_RECLAIM_COMPLETE))
        return FALSE;

    /* rca_one_fs = 0 indicates that the reclaim applies to all filesystems */
    return xdr_bool(xdr, &zero);
}

static bool_t decode_op_reclaim_complete(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_reclaim_complete_res *res = (nfs41_reclaim_complete_res*)resop->res;

    if (unexpected_op(resop->op, OP_RECLAIM_COMPLETE))
        return FALSE;

    return xdr_enum(xdr, (enum_t *)&res->status);
}


/*
 * OP_PUTFH
 */
static bool_t encode_op_putfh(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_putfh_args *args = (nfs41_putfh_args*)argop->arg;

    if (unexpected_op(argop->op, OP_PUTFH))
        return FALSE;

    return xdr_fh(xdr, &args->file->fh);
}

static bool_t decode_op_putfh(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_putfh_res *res = (nfs41_putfh_res*)resop->res;

    if (unexpected_op(resop->op, OP_PUTFH))
        return FALSE;

    return xdr_u_int32_t(xdr, &res->status);
}


/*
 * OP_PUTROOTFH
 */
static bool_t encode_op_putrootfh(
    XDR *xdr,
    nfs_argop4* argop)
{
    if (unexpected_op(argop->op, OP_PUTROOTFH))
        return FALSE;
    /* void */
    return TRUE;
}

static bool_t decode_op_putrootfh(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_putrootfh_res *res = (nfs41_putrootfh_res*)resop->res;

    if (unexpected_op(resop->op, OP_PUTROOTFH))
        return FALSE;

    return xdr_u_int32_t(xdr, &res->status);
}


/*
 * OP_GETFH
 */
static bool_t encode_op_getfh(
    XDR *xdr,
    nfs_argop4 *argop)
{
    if (unexpected_op(argop->op, OP_GETFH))
        return FALSE;

    /* void */
    return TRUE;
}

static bool_t decode_op_getfh(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_getfh_res *res = (nfs41_getfh_res*)resop->res;

    if (unexpected_op(resop->op, OP_GETFH))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (res->status == NFS4_OK)
        return xdr_fh(xdr, res->fh);

    return TRUE;
}


/*
 * OP_LOOKUP
 */
static bool_t encode_op_lookup(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_lookup_args *args = (nfs41_lookup_args*)argop->arg;

    if (unexpected_op(argop->op, OP_LOOKUP))
        return FALSE;

    return encode_component(xdr, args->name);
}

static bool_t decode_op_lookup(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_lookup_res *res = (nfs41_lookup_res*)resop->res;

    if (unexpected_op(resop->op, OP_LOOKUP))
        return FALSE;

    return xdr_u_int32_t(xdr, &res->status);
}


/*
 * OP_ACCESS
 */
static bool_t encode_op_access(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_access_args *args = (nfs41_access_args*)argop->arg;

    if (unexpected_op(argop->op, OP_ACCESS))
        return FALSE;

    return xdr_u_int32_t(xdr, &args->access);
}

static bool_t decode_op_access(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_access_res *res = (nfs41_access_res*)resop->res;

    if (unexpected_op(resop->op, OP_ACCESS))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (res->status == NFS4_OK)
    {
        if (!xdr_u_int32_t(xdr, &res->supported))
            return FALSE;

        return xdr_u_int32_t(xdr, &res->access);
    }
    return TRUE;
}


/*
 * OP_CLOSE
 */
static bool_t encode_op_close(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_op_close_args *args = (nfs41_op_close_args*)argop->arg;
    uint32_t zero = 0;

    if (unexpected_op(argop->op, OP_CLOSE))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &zero)) // This should be ignored by server
        return FALSE;

    return xdr_stateid4(xdr, &args->stateid->stateid);
}

static bool_t decode_op_close(
    XDR *xdr,
    nfs_resop4 *resop)
{
    stateid4 ignored;
    nfs41_op_close_res *res = (nfs41_op_close_res*)resop->res;

    if (unexpected_op(resop->op, OP_CLOSE))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (res->status == NFS4_OK)
        return xdr_stateid4(xdr, &ignored);

    return TRUE;
}


/*
 * OP_COMMIT
 */
static bool_t encode_op_commit(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_commit_args *args = (nfs41_commit_args*)argop->arg;

    if (unexpected_op(argop->op, OP_COMMIT))
        return FALSE;

    if (!xdr_u_hyper(xdr, &args->offset))
        return FALSE;

    return xdr_u_int32_t(xdr, &args->count);
}

static bool_t decode_op_commit(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_commit_res *res = (nfs41_commit_res*)resop->res;

    if (unexpected_op(resop->op, OP_COMMIT))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (res->status == NFS4_OK)
        return xdr_opaque(xdr, (char *)res->verf->verf, NFS4_VERIFIER_SIZE);

    return TRUE;
}


/*
 * OP_CREATE
 */
static bool_t encode_createtype4(
    XDR *xdr,
    createtype4 *ct)
{
    bool_t result = TRUE;
    const char *linkdata;

    if (!xdr_u_int32_t(xdr, &ct->type))
        return FALSE;

    switch (ct->type)
    {
    case NF4LNK:
        linkdata = ct->u.lnk.linkdata;
        result = xdr_bytes(xdr, (char**)&linkdata, &ct->u.lnk.linkdata_len,
            NFS4_OPAQUE_LIMIT);
        break;
    case NF4BLK:
    case NF4CHR:
        result = xdr_u_int32_t(xdr, &ct->u.devdata.specdata1);
        if (result == TRUE)
            result = xdr_u_int32_t(xdr, &ct->u.devdata.specdata2);
        break;
    default:
        // Some types need no further action
        break;
    }
    return result;
}

static bool_t encode_createattrs4(
    XDR *xdr,
    createattrs4* createattrs)
{
    fattr4 attrs;

    /* encode attribute values from createattrs->info into attrs.attr_vals */
    attrs.attr_vals_len = NFS4_OPAQUE_LIMIT;
    if (!encode_file_attrs(&attrs, &createattrs->info))
        return FALSE;

    return xdr_fattr4(xdr, &attrs);
}

static bool_t encode_op_create(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_create_args *args = (nfs41_create_args*)argop->arg;

    if (unexpected_op(argop->op, OP_CREATE))
        return FALSE;

    if (!encode_createtype4(xdr, &args->objtype))
        return FALSE;

    if (!encode_component(xdr, args->name))
        return FALSE;

    return encode_createattrs4(xdr, &args->createattrs);
}

static bool_t xdr_change_info4(
    XDR *xdr,
    change_info4 *cinfo)
{
    if (!xdr_bool(xdr, &cinfo->atomic))
        return FALSE;

    if (!xdr_u_hyper(xdr, &cinfo->before))
        return FALSE;

    return xdr_u_hyper(xdr, &cinfo->after);
}

static bool_t decode_op_create(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_create_res *res = (nfs41_create_res*)resop->res;

    if (unexpected_op(resop->op, OP_CREATE))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (res->status == NFS4_OK)
    {
        if (!xdr_change_info4(xdr, &res->cinfo))
            return FALSE;
        return xdr_bitmap4(xdr, &res->attrset);
    }
    return TRUE;
}


/*
 * OP_LINK
 */
static bool_t encode_op_link(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_link_args *args = (nfs41_link_args*)argop->arg;

    if (unexpected_op(argop->op, OP_LINK))
        return FALSE;

    return encode_component(xdr, args->newname);
}

static bool_t decode_op_link(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_link_res *res = (nfs41_link_res*)resop->res;

    if (unexpected_op(resop->op, OP_LINK))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (res->status == NFS4_OK)
        return xdr_change_info4(xdr, &res->cinfo);

    return TRUE;
}


/*
 * OP_LOCK
 */
static bool_t xdr_locker4(
    XDR *xdr,
    locker4 *locker)
{
    if (xdr->x_op != XDR_ENCODE) {
        eprintf("%s: xdr->x_op %d is not supported!\n",
            "xdr_locker4", xdr->x_op);
        return FALSE;
    }

    if (!xdr_bool(xdr, &locker->new_lock_owner))
        return FALSE;

    if (locker->new_lock_owner) {
        /* open_to_lock_owner4 open_owner */
        if (!xdr_u_int32_t(xdr, &locker->u.open_owner.open_seqid))
            return FALSE;

        if (!xdr_stateid4(xdr, &locker->u.open_owner.open_stateid->stateid))
            return FALSE;

        if (!xdr_u_int32_t(xdr, &locker->u.open_owner.lock_seqid))
            return FALSE;

        return xdr_state_owner4(xdr, locker->u.open_owner.lock_owner);
    } else {
        /* exist_lock_owner4 lock_owner */
        if (!xdr_stateid4(xdr, &locker->u.lock_owner.lock_stateid->stateid))
            return FALSE;

        return xdr_u_int32_t(xdr, &locker->u.lock_owner.lock_seqid);
    }
}

static bool_t encode_op_lock(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_lock_args *args = (nfs41_lock_args*)argop->arg;

    if (unexpected_op(argop->op, OP_LOCK))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &args->locktype))
        return FALSE;

    if (!xdr_bool(xdr, &args->reclaim))
        return FALSE;

    if (!xdr_u_hyper(xdr, &args->offset))
        return FALSE;

    if (!xdr_u_hyper(xdr, &args->length))
        return FALSE;

    return xdr_locker4(xdr, &args->locker);
}

static bool_t decode_lock_res_denied(
    XDR *xdr,
    lock_res_denied *denied)
{
    if (!xdr_u_hyper(xdr, &denied->offset))
        return FALSE;

    if (!xdr_u_hyper(xdr, &denied->length))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &denied->locktype))
        return FALSE;

    return xdr_state_owner4(xdr, &denied->owner);
}

static bool_t decode_op_lock(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_lock_res *res = (nfs41_lock_res*)resop->res;

    if (unexpected_op(resop->op, OP_LOCK))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    switch (res->status) {
    case NFS4_OK:
        return xdr_stateid4(xdr, res->u.resok4.lock_stateid);
        break;
    case NFS4ERR_DENIED:
        return decode_lock_res_denied(xdr, &res->u.denied);
        break;
    default:
        break;
    }

    return TRUE;
}


/*
 * OP_LOCKT
 */
static bool_t encode_op_lockt(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_lockt_args *args = (nfs41_lockt_args*)argop->arg;

    if (unexpected_op(argop->op, OP_LOCKT))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &args->locktype))
        return FALSE;

    if (!xdr_u_hyper(xdr, &args->offset))
        return FALSE;

    if (!xdr_u_hyper(xdr, &args->length))
        return FALSE;

    return xdr_state_owner4(xdr, args->owner);
}

static bool_t decode_op_lockt(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_lockt_res *res = (nfs41_lockt_res*)resop->res;

    if (unexpected_op(resop->op, OP_LOCKT))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (res->status == NFS4ERR_DENIED)
        return decode_lock_res_denied(xdr, &res->denied);

    return TRUE;
}


/*
 * OP_LOCKU
 */
static bool_t encode_op_locku(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_locku_args *args = (nfs41_locku_args*)argop->arg;

    if (unexpected_op(argop->op, OP_LOCKU))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &args->locktype))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &args->seqid))
        return FALSE;

    if (!xdr_stateid4(xdr, &args->lock_stateid->stateid))
        return FALSE;

    if (!xdr_u_hyper(xdr, &args->offset))
        return FALSE;

    return xdr_u_hyper(xdr, &args->length);
}

static bool_t decode_op_locku(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_locku_res *res = (nfs41_locku_res*)resop->res;

    if (unexpected_op(resop->op, OP_LOCKU))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (res->status == NFS4_OK)
        return xdr_stateid4(xdr, res->lock_stateid);

    return TRUE;
}


/*
 * OP_DELEGPURGE
 */
static bool_t encode_op_delegpurge(
    XDR *xdr,
    nfs_argop4 *argop)
{
    uint64_t zero = 0;

    if (unexpected_op(argop->op, OP_DELEGPURGE))
        return FALSE;

    /* The client SHOULD set the client field to zero,
     * and the server MUST ignore the clientid field. */
    return xdr_u_int64_t(xdr, &zero);
}

static bool_t decode_op_delegpurge(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_delegpurge_res *res = (nfs41_delegpurge_res*)resop->res;

    if (unexpected_op(resop->op, OP_DELEGPURGE))
        return FALSE;

    return xdr_u_int32_t(xdr, &res->status);
}


/*
 * OP_DELEGRETURN
 */
static bool_t encode_op_delegreturn(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_delegreturn_args *args = (nfs41_delegreturn_args*)argop->arg;

    if (unexpected_op(argop->op, OP_DELEGRETURN))
        return FALSE;

    return xdr_stateid4(xdr, &args->stateid->stateid);
}

static bool_t decode_op_delegreturn(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_delegreturn_res *res = (nfs41_delegreturn_res*)resop->res;

    if (unexpected_op(resop->op, OP_DELEGRETURN))
        return FALSE;

    return xdr_u_int32_t(xdr, &res->status);
}


/*
 * OP_GETATTR
 */
static bool_t encode_op_getattr(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_getattr_args *args = (nfs41_getattr_args*)argop->arg;

    if (unexpected_op(argop->op, OP_GETATTR))
        return FALSE;

    return xdr_bitmap4(xdr, args->attr_request);
}

static bool_t decode_file_attrs(
    XDR *xdr,
    fattr4 *attrs,
    nfs41_file_info *info)
{
    if (attrs->attrmask.count >= 1) {
        if (attrs->attrmask.arr[0] & FATTR4_WORD0_SUPPORTED_ATTRS) {
            if (!xdr_bitmap4(xdr, info->supported_attrs))
                return FALSE;
        }
        if (attrs->attrmask.arr[0] & FATTR4_WORD0_TYPE) {
            if (!xdr_u_int32_t(xdr, &info->type))
                return FALSE;
        }
        if (attrs->attrmask.arr[0] & FATTR4_WORD0_CHANGE) {
            if (!xdr_u_hyper(xdr, &info->change))
                return FALSE;
        }
        if (attrs->attrmask.arr[0] & FATTR4_WORD0_SIZE) {
            if (!xdr_u_hyper(xdr, &info->size))
                return FALSE;
        }
        if (attrs->attrmask.arr[0] & FATTR4_WORD0_LINK_SUPPORT) {
            if (!xdr_bool(xdr, &info->link_support))
                return FALSE;
        }
        if (attrs->attrmask.arr[0] & FATTR4_WORD0_SYMLINK_SUPPORT) {
            if (!xdr_bool(xdr, &info->symlink_support))
                return FALSE;
        }
        if (attrs->attrmask.arr[0] & FATTR4_WORD0_FSID) {
            if (!xdr_fsid(xdr, &info->fsid))
                return FALSE;
        }
        if (attrs->attrmask.arr[0] & FATTR4_WORD0_LEASE_TIME) {
            if (!xdr_u_int32_t(xdr, &info->lease_time))
                return FALSE;
        }
        if (attrs->attrmask.arr[0] & FATTR4_WORD0_RDATTR_ERROR) {
            if (!xdr_u_int32_t(xdr, &info->rdattr_error))
                return FALSE;
        }
        if (attrs->attrmask.arr[0] & FATTR4_WORD0_ACL) {
            nfsacl41 *acl = info->acl;
            if (!xdr_array(xdr, (char**)&acl->aces, &acl->count,
                32, sizeof(nfsace4), (xdrproc_t)xdr_nfsace4))
                return FALSE;
        }
        if (attrs->attrmask.arr[0] & FATTR4_WORD0_ACLSUPPORT) {
            if (!xdr_u_int32_t(xdr, &info->aclsupport))
                return FALSE;
        }
        if (attrs->attrmask.arr[0] & FATTR4_WORD0_CANSETTIME) {
            if (!xdr_bool(xdr, &info->cansettime))
                return FALSE;
        }
        if (attrs->attrmask.arr[0] & FATTR4_WORD0_CASE_INSENSITIVE) {
            if (!xdr_bool(xdr, &info->case_insensitive))
                return FALSE;
        }
        if (attrs->attrmask.arr[0] & FATTR4_WORD0_CASE_PRESERVING) {
            if (!xdr_bool(xdr, &info->case_preserving))
                return FALSE;
        }
        if (attrs->attrmask.arr[0] & FATTR4_WORD0_FILEID) {
            if (!xdr_u_hyper(xdr, &info->fileid))
                return FALSE;
        }
        if (attrs->attrmask.arr[0] & FATTR4_WORD0_FS_LOCATIONS) {
            if (!decode_fs_locations4(xdr, info->fs_locations))
                return FALSE;
        }
        if (attrs->attrmask.arr[0] & FATTR4_WORD0_MAXREAD) {
            if (!xdr_u_hyper(xdr, &info->maxread))
                return FALSE;
        }
        if (attrs->attrmask.arr[0] & FATTR4_WORD0_MAXWRITE) {
            if (!xdr_u_hyper(xdr, &info->maxwrite))
                return FALSE;
        }
    }
    if (attrs->attrmask.count >= 2) {
        if (attrs->attrmask.arr[1] & FATTR4_WORD1_MODE) {
            if (!xdr_u_int32_t(xdr, &info->mode))
                return FALSE;
        }
        if (attrs->attrmask.arr[1] & FATTR4_WORD1_NUMLINKS) {
            if (!xdr_u_int32_t(xdr, &info->numlinks))
                return FALSE;
        }
        if (attrs->attrmask.arr[1] & FATTR4_WORD1_OWNER) {
            char *ptr = &info->owner[0];
            uint32_t owner_len;
            if (!xdr_bytes(xdr, &ptr, &owner_len, 
                            NFS4_OPAQUE_LIMIT))
                return FALSE;
            info->owner[owner_len] = '\0';
        }
        if (attrs->attrmask.arr[1] & FATTR4_WORD1_OWNER_GROUP) {
            char *ptr = &info->owner_group[0];
            uint32_t owner_group_len;
            if (!xdr_bytes(xdr, &ptr, &owner_group_len, 
                            NFS4_OPAQUE_LIMIT))
                return FALSE;
            info->owner_group[owner_group_len] = '\0';
        }
        if (attrs->attrmask.arr[1] & FATTR4_WORD1_SPACE_AVAIL) {
            if (!xdr_u_hyper(xdr, &info->space_avail))
                return FALSE;
        }
        if (attrs->attrmask.arr[1] & FATTR4_WORD1_SPACE_FREE) {
            if (!xdr_u_hyper(xdr, &info->space_free))
                return FALSE;
        }
        if (attrs->attrmask.arr[1] & FATTR4_WORD1_SPACE_TOTAL) {
            if (!xdr_u_hyper(xdr, &info->space_total))
                return FALSE;
        }
        if (attrs->attrmask.arr[1] & FATTR4_WORD1_TIME_ACCESS) {
            if (!xdr_nfstime4(xdr, &info->time_access))
                return FALSE;
        }
        if (attrs->attrmask.arr[1] & FATTR4_WORD1_TIME_CREATE) {
            if (!xdr_nfstime4(xdr, &info->time_create))
                return FALSE;
        }
        if (attrs->attrmask.arr[1] & FATTR4_WORD1_TIME_DELTA) {
            if (!xdr_nfstime4(xdr, info->time_delta))
                return FALSE;
        }
        if (attrs->attrmask.arr[1] & FATTR4_WORD1_TIME_MODIFY) {
            if (!xdr_nfstime4(xdr, &info->time_modify))
                return FALSE;
        }
        if (attrs->attrmask.arr[1] & FATTR4_WORD1_DACL) {
            if (!xdr_nfsdacl41(xdr, info->acl))
                return FALSE;
        }
        if (attrs->attrmask.arr[1] & FATTR4_WORD1_FS_LAYOUT_TYPE) {
            if (!xdr_layout_types(xdr, &info->fs_layout_types))
                return FALSE;
        }
    }
    if (attrs->attrmask.count >= 3) {
        if (attrs->attrmask.arr[2] & FATTR4_WORD2_MDSTHRESHOLD) {
            if (!xdr_mdsthreshold(xdr, &info->mdsthreshold))
                return FALSE;
        }
    }
    return TRUE;
}

static bool_t decode_op_getattr(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_getattr_res *res = (nfs41_getattr_res*)resop->res;

    if (unexpected_op(resop->op, OP_GETATTR))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (res->status == NFS4_OK)
    {
        XDR attr_xdr;

        if (!xdr_fattr4(xdr, &res->obj_attributes))
            return FALSE;
        xdrmem_create(&attr_xdr, (char *)res->obj_attributes.attr_vals, res->obj_attributes.attr_vals_len, XDR_DECODE);
        return  decode_file_attrs(&attr_xdr, &res->obj_attributes, res->info);
    }
    return TRUE;
}


/*
 * OP_OPEN
 */
static bool_t encode_createhow4(
    XDR *xdr,
    createhow4 *ch)
{
    bool_t result = TRUE;

    if (!xdr_u_int32_t(xdr, &ch->mode))
        return FALSE;

    switch (ch->mode)
    {
    case UNCHECKED4:
    case GUARDED4:
        result = encode_createattrs4(xdr, &ch->createattrs);
        break;
    case EXCLUSIVE4:
        result = xdr_opaque(xdr, (char *)ch->createverf, NFS4_VERIFIER_SIZE);
        break;
    case EXCLUSIVE4_1:
        if (!xdr_opaque(xdr, (char *)ch->createverf, NFS4_VERIFIER_SIZE))
            return FALSE;
        if (!encode_createattrs4(xdr, &ch->createattrs))
            return FALSE;
        break;
    }
    return result;
}

static bool_t encode_openflag4(
    XDR *xdr,
    openflag4 *of)
{
    bool_t result = TRUE;

    if (!xdr_u_int32_t(xdr, &of->opentype))
        return FALSE;

    switch (of->opentype)
    {
    case OPEN4_CREATE:
        result = encode_createhow4(xdr, &of->how);
        break;
    default:
        break;
    }
    return result;
}

static bool_t encode_claim_deleg_cur(
    XDR *xdr,
    stateid4 *stateid,
    nfs41_component *name)
{
    if (!xdr_stateid4(xdr, stateid))
        return FALSE;
    return encode_component(xdr, name);
}

static bool_t encode_open_claim4(
    XDR *xdr,
    open_claim4 *oc)
{
    if (!xdr_u_int32_t(xdr, &oc->claim))
        return FALSE;

    switch (oc->claim)
    {
    case CLAIM_NULL:
        return encode_component(xdr, oc->u.null.filename);
    case CLAIM_PREVIOUS:
        return xdr_u_int32_t(xdr, &oc->u.prev.delegate_type);
    case CLAIM_FH:
        return TRUE; /* use current file handle */
    case CLAIM_DELEGATE_CUR:
        return encode_claim_deleg_cur(xdr,
            &oc->u.deleg_cur.delegate_stateid->stateid,
            oc->u.deleg_cur.name);
    case CLAIM_DELEG_CUR_FH:
        return xdr_stateid4(xdr,
            &oc->u.deleg_cur_fh.delegate_stateid->stateid);
    case CLAIM_DELEGATE_PREV:
        return encode_component(xdr, oc->u.deleg_prev.filename);
    case CLAIM_DELEG_PREV_FH:
        return TRUE; /* use current file handle */
    default:
        eprintf("encode_open_claim4: unsupported claim %d.\n",
            oc->claim);
        return FALSE;
    }
}

static bool_t encode_op_open(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_op_open_args *args = (nfs41_op_open_args*)argop->arg;

    if (unexpected_op(argop->op, OP_OPEN))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &args->seqid))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &args->share_access))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &args->share_deny))
        return FALSE;

    if (!xdr_state_owner4(xdr, args->owner))
        return FALSE;

    if (!encode_openflag4(xdr, &args->openhow))
        return FALSE;

    return encode_open_claim4(xdr, args->claim);
}

static bool_t decode_open_none_delegation4(
    XDR *xdr,
    open_delegation4 *delegation)
{
    enum_t why_no_deleg;
    bool_t will_signal;

    if (!xdr_enum(xdr, (enum_t*)&why_no_deleg))
        return FALSE;

    switch (why_no_deleg)
    {
    case WND4_CONTENTION:
    case WND4_RESOURCE:
        return xdr_bool(xdr, &will_signal);
    default:
        return TRUE;
    }
}

static bool_t decode_open_read_delegation4(
    XDR *xdr,
    open_delegation4 *delegation)
{
    if (!xdr_stateid4(xdr, &delegation->stateid))
        return FALSE;

    if (!xdr_bool(xdr, &delegation->recalled))
        return FALSE;

    return xdr_nfsace4(xdr, &delegation->permissions);
}

static bool_t decode_modified_limit4(
    XDR *xdr,
    uint64_t *filesize)
{
    uint32_t blocks, bytes_per_block;

    if (!xdr_u_int32_t(xdr, &blocks))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &bytes_per_block))
        return FALSE;

    *filesize = blocks * bytes_per_block;
    return TRUE;
}

enum limit_by4 {
    NFS_LIMIT_SIZE          = 1,
    NFS_LIMIT_BLOCKS        = 2
};

static bool_t decode_space_limit4(
    XDR *xdr,
    uint64_t *filesize)
{
    uint32_t limitby;

    if (!xdr_u_int32_t(xdr, &limitby))
        return FALSE;

    switch (limitby)
    {
    case NFS_LIMIT_SIZE:
        return xdr_u_hyper(xdr, filesize);
    case NFS_LIMIT_BLOCKS:
        return decode_modified_limit4(xdr, filesize);
    default:
        eprintf("decode_space_limit4: limitby %d invalid\n", limitby);
        return FALSE;
    }
}

static bool_t decode_open_write_delegation4(
    XDR *xdr,
    open_delegation4 *delegation)
{
    uint64_t size_limit;

    if (!xdr_stateid4(xdr, &delegation->stateid))
        return FALSE;

    if (!xdr_bool(xdr, &delegation->recalled))
        return FALSE;

    if (!decode_space_limit4(xdr, &size_limit))
        return FALSE;

    return xdr_nfsace4(xdr, &delegation->permissions);
}

static bool_t decode_open_res_ok(
    XDR *xdr,
    nfs41_op_open_res_ok *res)
{
    if (!xdr_stateid4(xdr, res->stateid))
        return FALSE;

    if (!xdr_change_info4(xdr, &res->cinfo))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->rflags))
        return FALSE;

    if (!xdr_bitmap4(xdr, &res->attrset))
        return FALSE;

    if (!xdr_enum(xdr, (enum_t*)&res->delegation->type))
        return FALSE;

    switch (res->delegation->type)
    {
    case OPEN_DELEGATE_NONE:
        return TRUE;
    case OPEN_DELEGATE_NONE_EXT:
        return decode_open_none_delegation4(xdr, res->delegation);
    case OPEN_DELEGATE_READ:
        return decode_open_read_delegation4(xdr, res->delegation);
    case OPEN_DELEGATE_WRITE:
        return decode_open_write_delegation4(xdr, res->delegation);
    default:
        eprintf("decode_open_res_ok: delegation type %d not "
            "supported.\n", res->delegation->type);
        return FALSE;
    }
}

static bool_t decode_op_open(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_op_open_res *res = (nfs41_op_open_res*)resop->res;

    if (unexpected_op(resop->op, OP_OPEN))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (res->status == NFS4_OK)
        return decode_open_res_ok(xdr, &res->resok4);

    return TRUE;
}


/*
 * OP_OPENATTR
 */
static bool_t encode_op_openattr(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_openattr_args *args = (nfs41_openattr_args*)argop->arg;

    if (unexpected_op(argop->op, OP_OPENATTR))
        return FALSE;

    return xdr_bool(xdr, &args->createdir);
}

static bool_t decode_op_openattr(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_openattr_res *res = (nfs41_openattr_res*)resop->res;

    if (unexpected_op(resop->op, OP_OPENATTR))
        return FALSE;

    return xdr_u_int32_t(xdr, &res->status);
}


/*
 * OP_READ
 */
static bool_t encode_op_read(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_read_args *args = (nfs41_read_args*)argop->arg;

    if (unexpected_op(argop->op, OP_READ))
        return FALSE;

    if (!xdr_stateid4(xdr, &args->stateid->stateid))
        return FALSE;

    if (!xdr_u_hyper(xdr, &args->offset))
        return FALSE;

    return xdr_u_int32_t(xdr, &args->count);
}

static bool_t decode_read_res_ok(
    XDR *xdr,
    nfs41_read_res_ok *res)
{
    unsigned char *data = res->data;

    if (!xdr_bool(xdr, &res->eof))
        return FALSE;

    return xdr_bytes(xdr, (char **)&data, &res->data_len, NFS41_MAX_FILEIO_SIZE);
}

static bool_t decode_op_read(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_read_res *res = (nfs41_read_res*)resop->res;

    if (unexpected_op(resop->op, OP_READ))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (res->status == NFS4_OK)
        return decode_read_res_ok(xdr, &res->resok4);

    return TRUE;
}


/*
 * OP_READDIR
 */
static bool_t encode_op_readdir(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_readdir_args *args = (nfs41_readdir_args*)argop->arg;

    if (unexpected_op(argop->op, OP_READDIR))
        return FALSE;

    if (!xdr_u_hyper(xdr, &args->cookie.cookie))
        return FALSE;

    if (!xdr_opaque(xdr, (char *)args->cookie.verf, NFS4_VERIFIER_SIZE))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &args->dircount))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &args->maxcount))
        return FALSE;

    return xdr_bitmap4(xdr, args->attr_request);
}

typedef struct __readdir_entry_iterator {
    unsigned char   *buf_pos;
    uint32_t        remaining_len;
    uint32_t        *last_entry_offset;
    bool_t          ignore_the_rest;
    bool_t          has_next_entry;
} readdir_entry_iterator;

static bool_t decode_readdir_entry(
    XDR *xdr,
    readdir_entry_iterator *it)
{
    uint64_t cookie;
    unsigned char name[NFS4_OPAQUE_LIMIT];
    unsigned char *nameptr = &name[0];
    uint32_t name_len, entry_len;
    fattr4 attrs;

    /* decode into temporaries so we can determine if there's enough
     * room in the buffer for this entry */
    name_len = NFS4_OPAQUE_LIMIT;
    entry_len = (uint32_t)FIELD_OFFSET(nfs41_readdir_entry, name);
    attrs.attr_vals_len = NFS4_OPAQUE_LIMIT;

    if (!xdr_u_hyper(xdr, &cookie))
        return FALSE;

    if (!xdr_bytes(xdr, (char **)&nameptr, &name_len, NFS4_OPAQUE_LIMIT))
        return FALSE;

    if (!xdr_fattr4(xdr, &attrs))
        return FALSE;

    if (!xdr_bool(xdr, &it->has_next_entry))
        return FALSE;

    if (it->ignore_the_rest)
        return TRUE;

    name_len += 1; /* account for null terminator */
    if (entry_len + name_len <= it->remaining_len)
    {
        XDR fattr_xdr;
        nfs41_readdir_entry *entry = (nfs41_readdir_entry*)it->buf_pos;
        entry->cookie = cookie;
        entry->name_len = name_len;

        if (it->has_next_entry)
            entry->next_entry_offset = entry_len + name_len;
        else
            entry->next_entry_offset = 0;

        xdrmem_create(&fattr_xdr, (char *)attrs.attr_vals, attrs.attr_vals_len, XDR_DECODE);
        if (!(decode_file_attrs(&fattr_xdr, &attrs, &entry->attr_info)))
            entry->attr_info.rdattr_error = NFS4ERR_BADXDR;
        StringCchCopyA(entry->name, name_len, (STRSAFE_LPCSTR)name);

        it->buf_pos += entry_len + name_len;
        it->remaining_len -= entry_len + name_len;
        it->last_entry_offset = &entry->next_entry_offset;
    }
    else if (it->last_entry_offset)
    {
        *(it->last_entry_offset) = 0;
        it->ignore_the_rest = 1;
    }

    return TRUE;
}

static bool_t decode_readdir_list(
    XDR *xdr,
    nfs41_readdir_list *dirs)
{
    readdir_entry_iterator iter;
    iter.buf_pos = dirs->entries;
    iter.remaining_len = dirs->entries_len;
    iter.last_entry_offset = NULL;
    iter.ignore_the_rest = 0;
    iter.has_next_entry = 0;

    if (!xdr_bool(xdr, &dirs->has_entries))
        return FALSE;

    if (dirs->has_entries)
    {
        do {
            if (!decode_readdir_entry(xdr, &iter))
                return FALSE;

        } while (iter.has_next_entry);
    }
    dirs->entries_len -= iter.remaining_len;

    if (!xdr_bool(xdr, &dirs->eof))
        return FALSE;

    /* reset eof if we couldn't fit everything in the buffer */
    if (iter.ignore_the_rest)
        dirs->eof = 0;
    return TRUE;
}

static bool_t decode_op_readdir(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_readdir_res *res = (nfs41_readdir_res*)resop->res;

    if (unexpected_op(resop->op, OP_READDIR))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (res->status == NFS4_OK) {
        if (!xdr_opaque(xdr, (char *)res->cookieverf, NFS4_VERIFIER_SIZE))
            return FALSE;
        return decode_readdir_list(xdr, &res->reply);
    }
    return TRUE;
}


/*
 * OP_READLINK
 */
static bool_t encode_op_readlink(
    XDR *xdr,
    nfs_argop4 *argop)
{
    if (unexpected_op(argop->op, OP_READLINK))
        return FALSE;

    /* void */
    return TRUE;
}

static bool_t decode_op_readlink(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_readlink_res *res = (nfs41_readlink_res*)resop->res;

    if (unexpected_op(resop->op, OP_READLINK))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (res->status == NFS4_OK) {
        char *link = res->link;
        return xdr_bytes(xdr, &link, &res->link_len, res->link_len);
    }

    return TRUE;
}


/*
 * OP_REMOVE
 */
static bool_t encode_op_remove(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_remove_args *args = (nfs41_remove_args*)argop->arg;

    if (unexpected_op(argop->op, OP_REMOVE))
        return FALSE;

    return encode_component(xdr, args->target);
}

static bool_t decode_op_remove(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_remove_res *res = (nfs41_remove_res*)resop->res;

    if (unexpected_op(resop->op, OP_REMOVE))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (res->status == NFS4_OK)
        return xdr_change_info4(xdr, &res->cinfo);

    return TRUE;
}


/*
 * OP_RENAME
 */
static bool_t encode_op_rename(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_rename_args *args = (nfs41_rename_args*)argop->arg;

    if (unexpected_op(argop->op, OP_RENAME))
        return FALSE;

    if (!encode_component(xdr, args->oldname))
        return FALSE;

    return encode_component(xdr, args->newname);
}

static bool_t decode_op_rename(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_rename_res *res = (nfs41_rename_res*)resop->res;

    if (unexpected_op(resop->op, OP_RENAME))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (res->status == NFS4_OK)
    {
        if (!xdr_change_info4(xdr, &res->source_cinfo))
            return FALSE;
        return xdr_change_info4(xdr, &res->target_cinfo);
    }
    return TRUE;
}


/*
 * OP_RESTOREFH
 */
static bool_t encode_op_restorefh(
    XDR *xdr,
    nfs_argop4 *argop)
{
    if (unexpected_op(argop->op, OP_RESTOREFH))
        return FALSE;

    /* void */
    return TRUE;
}

static bool_t decode_op_restorefh(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_restorefh_res *res = (nfs41_restorefh_res*)resop->res;

    if (unexpected_op(resop->op, OP_RESTOREFH))
        return FALSE;

    return xdr_u_int32_t(xdr, &res->status);
}


/*
 * OP_SAVEFH
 */
static bool_t encode_op_savefh(
    XDR *xdr,
    nfs_argop4 *argop)
{
    if (unexpected_op(argop->op, OP_SAVEFH))
        return FALSE;

    /* void */
    return TRUE;
}

static bool_t decode_op_savefh(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_savefh_res *res = (nfs41_savefh_res*)resop->res;

    if (unexpected_op(resop->op, OP_SAVEFH))
        return FALSE;

    return xdr_u_int32_t(xdr, &res->status);
}


/*
 * OP_SETATTR
 */
static bool_t encode_file_attrs(
    fattr4 *attrs,
    nfs41_file_info *info)
{
    uint32_t i;
    XDR localxdr;

    xdrmem_create(&localxdr, (char *)attrs->attr_vals, NFS4_OPAQUE_LIMIT, XDR_ENCODE);

    attrs->attr_vals_len = 0;
    ZeroMemory(&attrs->attrmask, sizeof(bitmap4));
    attrs->attrmask.count = info->attrmask.count;

    if (info->attrmask.count > 0) {
        if (info->attrmask.arr[0] & FATTR4_WORD0_SIZE) {
            if (!xdr_u_hyper(&localxdr, &info->size))
                return FALSE;
            attrs->attrmask.arr[0] |= FATTR4_WORD0_SIZE;
        }
        if (info->attrmask.arr[0] & FATTR4_WORD0_ACL) {
            if (!xdr_nfsacl41(&localxdr, info->acl))
                return FALSE;
            attrs->attrmask.arr[0] |= FATTR4_WORD0_ACL;
        }
        if (info->attrmask.arr[0] & FATTR4_WORD0_HIDDEN) {
            if (!xdr_bool(&localxdr, &info->hidden))
                return FALSE;
            attrs->attrmask.arr[0] |= FATTR4_WORD0_HIDDEN;
        }
    }
    if (info->attrmask.count > 1) {
        if (info->attrmask.arr[1] & FATTR4_WORD1_MODE) {
            if (!xdr_u_int32_t(&localxdr, &info->mode))
                return FALSE;
            attrs->attrmask.arr[1] |= FATTR4_WORD1_MODE;
        }
        if (info->attrmask.arr[1] & FATTR4_WORD1_TIME_ACCESS_SET) {
            if (!xdr_settime4(&localxdr, &info->time_access, info->time_delta))
                return FALSE;
            attrs->attrmask.arr[1] |= FATTR4_WORD1_TIME_ACCESS_SET;
        }
        if (info->attrmask.arr[1] & FATTR4_WORD1_TIME_CREATE) {
            if (!xdr_nfstime4(&localxdr, &info->time_create))
                return FALSE;
            attrs->attrmask.arr[1] |= FATTR4_WORD1_TIME_CREATE;
        }
        if (info->attrmask.arr[1] & FATTR4_WORD1_TIME_MODIFY_SET) {
            if (!xdr_settime4(&localxdr, &info->time_modify, info->time_delta))
                return FALSE;
            attrs->attrmask.arr[1] |= FATTR4_WORD1_TIME_MODIFY_SET;
        }
        if (info->attrmask.arr[1] & FATTR4_WORD1_OWNER) {
            char *ptr = &info->owner[0];
            uint32_t owner_len = (uint32_t)strlen(info->owner);
            if (!xdr_bytes(&localxdr, &ptr, &owner_len, 
                            NFS4_OPAQUE_LIMIT))
                return FALSE;
            attrs->attrmask.arr[1] |= FATTR4_WORD1_OWNER;
        }
        if (info->attrmask.arr[1] & FATTR4_WORD1_OWNER_GROUP) {
            char *ptr = &info->owner_group[0];
            uint32_t owner_group_len = (uint32_t)strlen(info->owner_group);
            if (!xdr_bytes(&localxdr, &ptr, &owner_group_len, 
                            NFS4_OPAQUE_LIMIT))
                return FALSE;
            attrs->attrmask.arr[1] |= FATTR4_WORD1_OWNER_GROUP;
        }
    }
    if (info->attrmask.count > 2) {
        if (info->attrmask.arr[2] & FATTR4_WORD2_MODE_SET_MASKED) {
            if (!xdr_u_int32_t(&localxdr, &info->mode))
                return FALSE;
            if (!xdr_u_int32_t(&localxdr, &info->mode_mask))
                return FALSE;
            attrs->attrmask.arr[2] |= FATTR4_WORD2_MODE_SET_MASKED;
        }
    }

    /* warn if we try to set attributes that aren't handled */
    for (i = 0; i < info->attrmask.count; i++)
        if (attrs->attrmask.arr[i] != info->attrmask.arr[i])
            eprintf("encode_file_attrs() attempted to encode extra "
                "attributes in arr[%d]: encoded %d, but wanted %d.\n",
                i, attrs->attrmask.arr[i], info->attrmask.arr[i]);

    attrs->attr_vals_len = xdr_getpos(&localxdr);
    return TRUE;
}

static bool_t encode_op_setattr(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_setattr_args *args = (nfs41_setattr_args*)argop->arg;
    fattr4 attrs;

    if (unexpected_op(argop->op, OP_SETATTR))
        return FALSE;

    if (!xdr_stateid4(xdr, &args->stateid->stateid))
        return FALSE;

    /* encode attribute values from args->info into attrs.attr_vals */
    attrs.attr_vals_len = NFS4_OPAQUE_LIMIT;
    if (!encode_file_attrs(&attrs, args->info))
        return FALSE;

    return xdr_fattr4(xdr, &attrs);
}

static bool_t decode_op_setattr(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_setattr_res *res = (nfs41_setattr_res*)resop->res;

    if (unexpected_op(resop->op, OP_SETATTR))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (res->status == NFS4_OK)
        return xdr_bitmap4(xdr, &res->attrsset);

    return TRUE;
}


/*
 * OP_WANT_DELEGATION
 */
static bool_t encode_op_want_delegation(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_want_delegation_args *args = (nfs41_want_delegation_args*)argop->arg;

    if (unexpected_op(argop->op, OP_WANT_DELEGATION))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &args->want))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &args->claim->claim))
        return FALSE;

    return args->claim->claim != CLAIM_PREVIOUS ||
        xdr_u_int32_t(xdr, &args->claim->prev_delegate_type);
}

static bool_t decode_op_want_delegation(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_want_delegation_res *res = (nfs41_want_delegation_res*)resop->res;

    if (unexpected_op(resop->op, OP_WANT_DELEGATION))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (res->status)
        return TRUE;

    if (!xdr_enum(xdr, (enum_t*)&res->delegation->type))
        return FALSE;

    switch (res->delegation->type)
    {
    case OPEN_DELEGATE_NONE:
        return TRUE;
    case OPEN_DELEGATE_NONE_EXT:
        return decode_open_none_delegation4(xdr, res->delegation);
    case OPEN_DELEGATE_READ:
        return decode_open_read_delegation4(xdr, res->delegation);
    case OPEN_DELEGATE_WRITE:
        return decode_open_write_delegation4(xdr, res->delegation);
    default:
        eprintf("decode_open_res_ok: delegation type %d not "
            "supported.\n", res->delegation->type);
        return FALSE;
    }
}


/*
 * OP_FREE_STATEID
 */
static bool_t encode_op_free_stateid(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_free_stateid_args *args = (nfs41_free_stateid_args*)argop->arg;

    if (unexpected_op(argop->op, OP_FREE_STATEID))
        return FALSE;

    return xdr_stateid4(xdr, args->stateid);
}

static bool_t decode_op_free_stateid(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_free_stateid_res *res = (nfs41_free_stateid_res*)resop->res;

    if (unexpected_op(resop->op, OP_FREE_STATEID))
        return FALSE;

    return xdr_u_int32_t(xdr, &res->status);
}


/*
 * OP_TEST_STATEID
 */
static bool_t encode_op_test_stateid(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_test_stateid_args *args = (nfs41_test_stateid_args*)argop->arg;

    if (unexpected_op(argop->op, OP_TEST_STATEID))
        return FALSE;

    return xdr_array(xdr, (char**)&args->stateids, &args->count,
        args->count, sizeof(stateid_arg), (xdrproc_t)xdr_stateid4);
}

static bool_t decode_op_test_stateid(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_test_stateid_res *res = (nfs41_test_stateid_res*)resop->res;

    if (unexpected_op(resop->op, OP_TEST_STATEID))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (res->status == NFS4_OK) {
        return xdr_array(xdr, (char**)&res->resok.status, &res->resok.count,
            res->resok.count, sizeof(uint32_t), (xdrproc_t)xdr_u_int32_t);
    }
    return TRUE;
}


/*
 * OP_WRITE
 */
static bool_t encode_op_write(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_write_args *args = (nfs41_write_args*)argop->arg;
    unsigned char *data = args->data;

    if (unexpected_op(argop->op, OP_WRITE))
        return FALSE;

    if (!xdr_stateid4(xdr, &args->stateid->stateid))
        return FALSE;

    if (!xdr_u_hyper(xdr, &args->offset))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &args->stable))
        return FALSE;

    return xdr_bytes(xdr, (char **)&data, &args->data_len, NFS41_MAX_FILEIO_SIZE);
}

static bool_t xdr_write_verf(
    XDR *xdr,
    nfs41_write_verf *verf)
{
    if (!xdr_enum(xdr, (enum_t *)&verf->committed))
        return FALSE;

    return xdr_opaque(xdr, (char *)verf->verf, NFS4_VERIFIER_SIZE);
}

static bool_t xdr_write_res_ok(
    XDR *xdr,
    nfs41_write_res_ok *res)
{
    if (!xdr_u_int32_t(xdr, &res->count))
        return FALSE;

    return xdr_write_verf(xdr, res->verf);
}

static bool_t decode_op_write(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_write_res *res = (nfs41_write_res*)resop->res;

    if (unexpected_op(resop->op, OP_WRITE))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (res->status == NFS4_OK)
        return xdr_write_res_ok(xdr, &res->resok4);

    return TRUE;
}

/* 
 * OP_SECINFO_NO_NAME
 */
static bool_t xdr_secinfo(
    XDR *xdr,
    nfs41_secinfo_info *secinfo)
{
    if (!xdr_u_int32_t(xdr, &secinfo->sec_flavor))
        return FALSE;
    if (secinfo->sec_flavor == RPCSEC_GSS) {
        char *p = secinfo->oid;
        if (!xdr_bytes(xdr, (char **)&p, &secinfo->oid_len, MAX_OID_LEN))
            return FALSE;
        if (!xdr_u_int32_t(xdr, &secinfo->qop))
            return FALSE;
        if (!xdr_enum(xdr, (enum_t *)&secinfo->type))
            return FALSE;
    }
    return TRUE;
}

static bool_t encode_op_secinfo_noname(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_secinfo_noname_args *args = (nfs41_secinfo_noname_args *)argop->arg;

    if (unexpected_op(argop->op, OP_SECINFO_NO_NAME))
        return FALSE;

    if (!xdr_enum(xdr, (enum_t *)&args->type))
        return FALSE;

    return TRUE;
}

static bool_t decode_op_secinfo_noname(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_secinfo_noname_res *res = (nfs41_secinfo_noname_res *)resop->res;
    nfs41_secinfo_info *secinfo = res->secinfo;
    if (unexpected_op(resop->op, OP_SECINFO_NO_NAME))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (res->status == NFS4_OK)
        return xdr_array(xdr, (char**)&secinfo, &res->count,
            MAX_SECINFOS, sizeof(nfs41_secinfo_info), (xdrproc_t)xdr_secinfo);

    return TRUE;
}

/* 
 * OP_SECINFO
 */
static bool_t encode_op_secinfo(
    XDR *xdr,
    nfs_argop4 *argop)
{
    nfs41_secinfo_args *args = (nfs41_secinfo_args *)argop->arg;

    if (unexpected_op(argop->op, OP_SECINFO))
        return FALSE;

    if (!encode_component(xdr, args->name))
        return FALSE;

    return TRUE;
}

static bool_t decode_op_secinfo(
    XDR *xdr,
    nfs_resop4 *resop)
{
    nfs41_secinfo_noname_res *res = (nfs41_secinfo_noname_res *)resop->res;
    nfs41_secinfo_info *secinfo = res->secinfo;

    if (unexpected_op(resop->op, OP_SECINFO))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (res->status == NFS4_OK)
        return xdr_array(xdr, (char**)&secinfo, &res->count,
            MAX_SECINFOS, sizeof(nfs41_secinfo_info), (xdrproc_t)xdr_secinfo);

    return TRUE;
}
/*
 * OP_GETDEVICEINFO
 */
static bool_t encode_op_getdeviceinfo(
    XDR *xdr,
    nfs_argop4 *argop)
{
    pnfs_getdeviceinfo_args *args = (pnfs_getdeviceinfo_args*)argop->arg;

    if (unexpected_op(argop->op, OP_GETDEVICEINFO))
        return FALSE;

    if (!xdr_opaque(xdr, (char *)args->deviceid, PNFS_DEVICEID_SIZE))
        return FALSE;

    if (!xdr_enum(xdr, (enum_t *)&args->layout_type))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &args->maxcount))
        return FALSE;

    return xdr_bitmap4(xdr, &args->notify_types);
}

static bool_t xdr_stripe_indices(
    XDR *xdr,
    pnfs_stripe_indices *indices)
{
    uint32_t i, count;

    if (!xdr_u_int32_t(xdr, &count))
        return FALSE;

    if (count && count != indices->count) {
        uint32_t *tmp;
        tmp = realloc(indices->arr, count * sizeof(uint32_t));
        if (tmp == NULL)
            return FALSE;
        indices->arr = tmp;
        ZeroMemory(indices->arr, count * sizeof(uint32_t));
        indices->count = count;
    }
    
    for (i = 0; i < indices->count; i++) {
        if (!xdr_u_int32_t(xdr, &indices->arr[i]))
            return FALSE;
    }
    return TRUE;
}

static bool_t xdr_pnfs_addr(
    XDR *xdr,
    netaddr4 *addr)
{
    uint32_t len;
    char *netid = addr->netid;
    char *uaddr = addr->uaddr;

    if (xdr->x_op == XDR_ENCODE)
        len = sizeof(addr->netid);
    if (!xdr_bytes(xdr, &netid, &len, NFS41_NETWORK_ID_LEN))
        return FALSE;

    if (xdr->x_op == XDR_DECODE) {
        if (len < NFS41_NETWORK_ID_LEN)
            addr->netid[len] = 0;
        else
            addr->netid[NFS41_NETWORK_ID_LEN] = 0;
    }

    if (xdr->x_op == XDR_ENCODE)
        len = sizeof(addr->uaddr);
    if (!xdr_bytes(xdr, &uaddr, &len, NFS41_UNIVERSAL_ADDR_LEN))
        return FALSE;

    if (xdr->x_op == XDR_DECODE){
        if (len < NFS41_UNIVERSAL_ADDR_LEN)
            addr->uaddr[len] = 0;
        else
            addr->uaddr[NFS41_UNIVERSAL_ADDR_LEN] = 0;
    }

    return TRUE;
}

static bool_t xdr_multi_addr(
    XDR *xdr,
    multi_addr4 *list)
{
    netaddr4 dummy, *dest;
    uint32_t i;

    if (!xdr_u_int32_t(xdr, &list->count))
        return FALSE;

    for (i = 0; i < list->count; i++) {
        /* if there are too many addrs, decode the extras into 'dummy' */
        dest = i < NFS41_ADDRS_PER_SERVER ? &list->arr[i] : &dummy;

        if (!xdr_pnfs_addr(xdr, dest))
            return FALSE;
    }
    return TRUE;
}

static bool_t xdr_data_server_list(
    XDR *xdr,
    pnfs_data_server_list *servers)
{
    uint32_t i, count;

    if (!xdr_u_int32_t(xdr, &count))
        return FALSE;

    if (count && count != servers->count) {
        pnfs_data_server *tmp;
        /* clear data server clients; they're still cached with nfs41_root,
         * so pnfs_data_server_client() will look them up again */
        for (i = 0; i < servers->count; i++)
            servers->arr[i].client = NULL;

        tmp = realloc(servers->arr, count * sizeof(pnfs_data_server));
        if (tmp == NULL) 
            return FALSE;
        servers->arr = tmp;
        ZeroMemory(servers->arr, count * sizeof(pnfs_data_server));
        for (i = servers->count; i < count; i++) /* initialize new elements */
            InitializeSRWLock(&servers->arr[i].lock);
        servers->count = count;
    }

    for (i = 0; i < servers->count; i++) {
        if (!xdr_multi_addr(xdr, &servers->arr[i].addrs))
            return FALSE;
    }
    return TRUE;
}

static bool_t xdr_file_device(
    XDR *xdr,
    pnfs_file_device *device)
{
    if (!xdr_stripe_indices(xdr, &device->stripes))
        return FALSE;

    return xdr_data_server_list(xdr, &device->servers);
}

static bool_t decode_getdeviceinfo_ok(
    XDR *xdr,
    pnfs_getdeviceinfo_res_ok *res_ok)
{
    u_int32_t len_ignored;

    if (!xdr_enum(xdr, (enum_t *)&res_ok->device->device.type))
        return FALSE;

    if (res_ok->device->device.type != PNFS_LAYOUTTYPE_FILE)
        return FALSE;

    if (!xdr_u_int32_t(xdr, &len_ignored))
        return FALSE;

    if (!xdr_file_device(xdr, res_ok->device))
        return FALSE;

    return xdr_bitmap4(xdr, &res_ok->notification);
}

static bool_t decode_op_getdeviceinfo(
    XDR *xdr,
    nfs_resop4 *resop)
{
    pnfs_getdeviceinfo_res *res = (pnfs_getdeviceinfo_res*)resop->res;

    if (unexpected_op(resop->op, OP_GETDEVICEINFO))
        return FALSE;

    if (!xdr_u_int32_t(xdr, (uint32_t *)&res->status))
        return FALSE;

    switch (res->status) {
    case NFS4_OK:
        return decode_getdeviceinfo_ok(xdr, &res->u.res_ok);
        break;
    case NFS4ERR_TOOSMALL:
        {
            uint32_t ignored;
            return xdr_u_int32_t(xdr, &ignored);
        }
        break;
    }
    return TRUE;
}


/*
 * OP_LAYOUTCOMMIT
 */
static bool_t encode_op_layoutcommit(
    XDR *xdr,
    nfs_argop4 *argop)
{
    pnfs_layoutcommit_args *args = (pnfs_layoutcommit_args*)argop->arg;
    bool_t false_bool = FALSE;
    bool_t true_bool = TRUE;
    enum_t pnfs_layout = PNFS_LAYOUTTYPE_FILE;
    uint32_t zero = 0;

    if (unexpected_op(argop->op, OP_LAYOUTCOMMIT))
        return FALSE;

    if (!xdr_u_hyper(xdr, &args->offset))
        return FALSE;

    if (!xdr_u_hyper(xdr, &args->length))
        return FALSE;

    if (!xdr_bool(xdr, &false_bool)) /* loca_reclaim = 0 */
        return FALSE;

    if (!xdr_stateid4(xdr, args->stateid))
        return FALSE;

    /* loca_last_write_offset */
    if (args->new_offset) {
        if (!xdr_bool(xdr, &true_bool))
            return FALSE;

        if (!xdr_u_hyper(xdr, args->new_offset))
            return FALSE;
    } else {
        if (!xdr_bool(xdr, &false_bool))
            return FALSE;
    }

    /* loca_time_modify */
    if (args->new_time) {
        if (!xdr_bool(xdr, &true_bool))
            return FALSE;

        if (!xdr_nfstime4(xdr, args->new_time))
            return FALSE;
    } else {
        if (!xdr_bool(xdr, &false_bool))
            return FALSE;
    }

    /* loca_layoutupdate */
    if (!xdr_enum(xdr, &pnfs_layout))
        return FALSE;

    return xdr_u_int32_t(xdr, &zero);
}

static bool_t decode_op_layoutcommit(
    XDR *xdr,
    nfs_resop4 *resop)
{
    pnfs_layoutcommit_res *res = (pnfs_layoutcommit_res*)resop->res;

    if (unexpected_op(resop->op, OP_LAYOUTCOMMIT))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (res->status == NFS4_OK) {
        if (!xdr_bool(xdr, &res->has_new_size))
            return FALSE;

        if (res->has_new_size)
            if (!xdr_u_hyper(xdr, &res->new_size))
                return FALSE;
    }
    return TRUE;
}

/*
 * OP_LAYOUTGET
 */
static bool_t encode_op_layoutget(
    XDR *xdr,
    nfs_argop4 *argop)
{
    pnfs_layoutget_args *args = (pnfs_layoutget_args*)argop->arg;

    if (unexpected_op(argop->op, OP_LAYOUTGET))
        return FALSE;

    if (!xdr_bool(xdr, &args->signal_layout_avail))
        return FALSE;

    if (!xdr_u_int32_t(xdr, (u_int32_t *)&args->layout_type))
        return FALSE;

    if (!xdr_u_int32_t(xdr, (u_int32_t *)&args->iomode))
        return FALSE;

    if (!xdr_u_hyper(xdr, &args->offset))
        return FALSE;

    if (!xdr_u_hyper(xdr, &args->length))
        return FALSE;

    if (!xdr_u_hyper(xdr, &args->minlength))
        return FALSE;

    if (!xdr_stateid4(xdr, &args->stateid->stateid))
        return FALSE;

    return xdr_u_int32_t(xdr, &args->maxcount);
}

static bool_t decode_file_layout_handles(
    XDR *xdr,
    pnfs_file_layout_handles *handles)
{
    uint32_t i, count;

    if (!xdr_u_int32_t(xdr, &count))
        return FALSE;

    if (count && count != handles->count) {
        nfs41_path_fh *tmp;
        tmp = realloc(handles->arr, count * sizeof(nfs41_path_fh));
        if (tmp == NULL)
            return FALSE;
        handles->arr = tmp;
        ZeroMemory(handles->arr, count * sizeof(nfs41_path_fh));
        handles->count = count;
    }
    
    for (i = 0; i < handles->count; i++) {
        if (!xdr_fh(xdr, &handles->arr[i].fh))
            return FALSE;
    }
    return TRUE;
}

static bool_t decode_file_layout(
    XDR *xdr,
    struct list_entry *list,
    pnfs_layout *base)
{
    pnfs_file_layout *layout;
    u_int32_t len_ignored;

    if (!xdr_u_int32_t(xdr, &len_ignored))
        return FALSE;

    layout = calloc(1, sizeof(pnfs_file_layout));
    if (layout == NULL)
        return FALSE;

    layout->layout.offset = base->offset;
    layout->layout.length = base->length;
    layout->layout.iomode = base->iomode;
    layout->layout.type = base->type;
    list_init(&layout->layout.entry);

    if (!xdr_opaque(xdr, (char *)layout->deviceid, PNFS_DEVICEID_SIZE))
        goto out_error;

    if (!xdr_u_int32_t(xdr, &layout->util))
        goto out_error;

    if (!xdr_u_int32_t(xdr, &layout->first_index))
        goto out_error;

    if (!xdr_u_hyper(xdr, &layout->pattern_offset))
        goto out_error;

    if (!decode_file_layout_handles(xdr, &layout->filehandles))
        goto out_error;

    list_add_tail(list, &layout->layout.entry);
    return TRUE;

out_error:
    free(layout);
    return FALSE;
}

static bool_t decode_layout(
    XDR *xdr,
    struct list_entry *list)
{
    pnfs_layout layout;

    if (!xdr_u_hyper(xdr, &layout.offset))
        return FALSE;

    if (!xdr_u_hyper(xdr, &layout.length))
        return FALSE;

    if (!xdr_enum(xdr, (enum_t *)&layout.iomode))
        return FALSE;

    if (!xdr_enum(xdr, (enum_t *)&layout.type))
        return FALSE;

    switch (layout.type) {
    case PNFS_LAYOUTTYPE_FILE:
        return decode_file_layout(xdr, list, &layout);

    default:
        eprintf("%s: received non-FILE layout type, %d\n",
            "decode_file_layout", layout.type);
    }
    return FALSE;
}

static bool_t decode_layout_res_ok(
    XDR *xdr,
    pnfs_layoutget_res_ok *res)
{
    uint32_t i;

    if (!xdr_bool(xdr, &res->return_on_close))
        return FALSE;

    if (!xdr_stateid4(xdr, &res->stateid))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &res->count))
        return FALSE;

    for (i = 0; i < res->count; i++)
        if (!decode_layout(xdr, &res->layouts))
            return FALSE;
    return TRUE;
}

static bool_t decode_op_layoutget(
    XDR *xdr,
    nfs_resop4 *resop)
{
    pnfs_layoutget_res *res = (pnfs_layoutget_res*)resop->res;

    if (unexpected_op(resop->op, OP_LAYOUTGET))
        return FALSE;

    if (!xdr_u_int32_t(xdr, (uint32_t *)&res->status))
        return FALSE;

    switch (res->status) {
    case NFS4_OK:
        return decode_layout_res_ok(xdr, res->u.res_ok);
    case NFS4ERR_LAYOUTTRYLATER:
        return xdr_bool(xdr, &res->u.will_signal_layout_avail);
    }
    return TRUE;
}


/*
 * OP_LAYOUTRETURN
 */
static bool_t encode_op_layoutreturn(
    XDR *xdr,
    nfs_argop4 *argop)
{
    pnfs_layoutreturn_args *args = (pnfs_layoutreturn_args*)argop->arg;

    if (unexpected_op(argop->op, OP_LAYOUTRETURN))
        return FALSE;

    if (!xdr_bool(xdr, &args->reclaim))
        return FALSE;

    if (!xdr_enum(xdr, (enum_t *)&args->type))
        return FALSE;

    if (!xdr_enum(xdr, (enum_t *)&args->iomode))
        return FALSE;

    if (!xdr_enum(xdr, (enum_t *)&args->return_type))
        return FALSE;

    if (args->return_type == PNFS_RETURN_FILE) {
        u_int32_t zero = 0;

        if (!xdr_u_hyper(xdr, &args->offset))
            return FALSE;

        if (!xdr_u_hyper(xdr, &args->length))
            return FALSE;

        if (!xdr_stateid4(xdr, args->stateid))
            return FALSE;

        return xdr_u_int32_t(xdr, &zero); /* size of lrf_body is 0 */
    } else {
        eprintf("%s: layout type (%d) is not PNFS_RETURN_FILE!\n",
            "encode_op_layoutreturn", args->return_type);
        return FALSE;
    }
}

static bool_t decode_op_layoutreturn(
    XDR *xdr,
    nfs_resop4 *resop)
{
    pnfs_layoutreturn_res *res = (pnfs_layoutreturn_res*)resop->res;

    if (unexpected_op(resop->op, OP_LAYOUTRETURN))
        return FALSE;

    if (!xdr_u_int32_t(xdr, (uint32_t *)&res->status))
        return FALSE;

    if (res->status == NFS4_OK) {
        if (!xdr_bool(xdr, &res->stateid_present))
            return FALSE;

        if (res->stateid_present)
            return xdr_stateid4(xdr, &res->stateid);
    }
    return TRUE;
}


/* op encode/decode table */
typedef bool_t (*nfs_op_encode_proc)(XDR*, nfs_argop4*);
typedef bool_t (*nfs_op_decode_proc)(XDR*, nfs_resop4*);

typedef struct __op_table_entry {
    nfs_op_encode_proc      encode;
    nfs_op_decode_proc      decode;
} op_table_entry;

/* table of encode/decode functions, indexed by operation number */
static const op_table_entry g_op_table[] = {
    { NULL, NULL }, /* 0 unused */
    { NULL, NULL }, /* 1 unused */
    { NULL, NULL }, /* 2 unused */
    { encode_op_access, decode_op_access }, /* OP_ACCESS = 3 */
    { encode_op_close, decode_op_close }, /* OP_CLOSE = 4 */
    { encode_op_commit, decode_op_commit }, /* OP_COMMIT = 5 */
    { encode_op_create, decode_op_create }, /* OP_CREATE = 6 */
    { encode_op_delegpurge, decode_op_delegpurge }, /* OP_DELEGPURGE = 7 */
    { encode_op_delegreturn, decode_op_delegreturn }, /* OP_DELEGRETURN = 8 */
    { encode_op_getattr, decode_op_getattr }, /* OP_GETATTR = 9 */
    { encode_op_getfh, decode_op_getfh }, /* OP_GETFH = 10 */
    { encode_op_link, decode_op_link }, /* OP_LINK = 11 */
    { encode_op_lock, decode_op_lock }, /* OP_LOCK = 12 */
    { encode_op_lockt, decode_op_lockt }, /* OP_LOCKT = 13 */
    { encode_op_locku, decode_op_locku }, /* OP_LOCKU = 14 */
    { encode_op_lookup, decode_op_lookup }, /* OP_LOOKUP = 15 */
    { NULL, NULL }, /* OP_LOOKUPP = 16 */
    { NULL, NULL }, /* OP_NVERIFY = 17 */
    { encode_op_open, decode_op_open }, /* OP_OPEN = 18 */
    { encode_op_openattr, decode_op_openattr }, /* OP_OPENATTR = 19 */
    { NULL, NULL }, /* OP_OPEN_CONFIRM = 20 */
    { NULL, NULL }, /* OP_OPEN_DOWNGRADE = 21 */
    { encode_op_putfh, decode_op_putfh }, /* OP_PUTFH = 22 */
    { NULL, NULL }, /* OP_PUTPUBFH = 23 */
    { encode_op_putrootfh, decode_op_putrootfh }, /* OP_PUTROOTFH = 24 */
    { encode_op_read, decode_op_read }, /* OP_READ = 25 */
    { encode_op_readdir, decode_op_readdir }, /* OP_READDIR = 26 */
    { encode_op_readlink, decode_op_readlink }, /* OP_READLINK = 27 */
    { encode_op_remove, decode_op_remove }, /* OP_REMOVE = 28 */
    { encode_op_rename, decode_op_rename }, /* OP_RENAME = 29 */
    { NULL, NULL }, /* OP_RENEW = 30 */
    { encode_op_restorefh, decode_op_restorefh }, /* OP_RESTOREFH = 31 */
    { encode_op_savefh, decode_op_savefh }, /* OP_SAVEFH = 32 */
    { encode_op_secinfo, decode_op_secinfo }, /* OP_SECINFO = 33 */
    { encode_op_setattr, decode_op_setattr }, /* OP_SETATTR = 34 */
    { NULL, NULL }, /* OP_SETCLIENTID = 35 */
    { NULL, NULL }, /* OP_SETCLIENTID_CONFIRM  = 36 */
    { NULL, NULL }, /* OP_VERIFY = 37 */
    { encode_op_write, decode_op_write }, /* OP_WRITE = 38 */
    { NULL, NULL }, /* OP_RELEASE_LOCKOWNER = 39 */
    { NULL, NULL }, /* OP_BACKCHANNEL_CTL = 40 */
    { encode_op_bind_conn_to_session, decode_op_bind_conn_to_session }, /* OP_BIND_CONN_TO_SESSION = 41 */
    { encode_op_exchange_id, decode_op_exchange_id }, /* OP_EXCHANGE_ID = 42 */
    { encode_op_create_session, decode_op_create_session }, /* OP_CREATE_SESSION = 43 */
    { encode_op_destroy_session, decode_op_destroy_session }, /* OP_DESTROY_SESSION = 44 */
    { encode_op_free_stateid, decode_op_free_stateid }, /* OP_FREE_STATEID = 45 */
    { NULL, NULL }, /* OP_GET_DIR_DELEGATION = 46 */
    { encode_op_getdeviceinfo, decode_op_getdeviceinfo }, /* OP_GETDEVICEINFO = 47 */
    { NULL, NULL }, /* OP_GETDEVICELIST = 48 */
    { encode_op_layoutcommit, decode_op_layoutcommit }, /* OP_LAYOUTCOMMIT = 49 */
    { encode_op_layoutget, decode_op_layoutget }, /* OP_LAYOUTGET = 50 */
    { encode_op_layoutreturn, decode_op_layoutreturn }, /* OP_LAYOUTRETURN = 51 */
    { encode_op_secinfo_noname, decode_op_secinfo_noname }, /* OP_SECINFO_NO_NAME = 52 */
    { encode_op_sequence, decode_op_sequence }, /* OP_SEQUENCE = 53 */
    { NULL, NULL }, /* OP_SET_SSV = 54 */
    { encode_op_test_stateid, decode_op_test_stateid }, /* OP_TEST_STATEID = 55 */
    { encode_op_want_delegation, decode_op_want_delegation }, /* OP_WANT_DELEGATION = 56 */
    { encode_op_destroy_clientid, decode_op_destroy_clientid }, /* OP_DESTROY_CLIENTID = 57 */
    { encode_op_reclaim_complete, decode_op_reclaim_complete }, /* OP_RECLAIM_COMPLETE = 58 */
};
static const uint32_t g_op_table_size = ARRAYSIZE(g_op_table);

static const op_table_entry* op_table_find(uint32_t op)
{
    return op >= g_op_table_size ? NULL : &g_op_table[op];
}


/*
 * COMPOUND
 */
bool_t nfs_encode_compound(
    XDR *xdr,
    caddr_t *pargs)
{
    unsigned char *tag;

    nfs41_compound_args *args = (nfs41_compound_args*)pargs;
    uint32_t i;
    const op_table_entry *entry;

    tag = args->tag;
    if (!xdr_bytes(xdr, (char **)&tag, &args->tag_len, NFS4_OPAQUE_LIMIT))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &args->minorversion))
        return FALSE;

    if (!xdr_u_int32_t(xdr, &args->argarray_count))
        return FALSE;

    for (i = 0; i < args->argarray_count; i++)
    {
        entry = op_table_find(args->argarray[i].op);
        if (entry == NULL || entry->encode == NULL)
            return FALSE;

        if (!xdr_u_int32_t(xdr, &args->argarray[i].op))
            return FALSE;
        if (!entry->encode(xdr, &args->argarray[i]))
            return FALSE;
    }
    return TRUE;
}

bool_t nfs_decode_compound(
    XDR *xdr,
    caddr_t *pres)
{
    nfs41_compound_res *res = (nfs41_compound_res*)pres;
    uint32_t i, expected_count, expected_op;
    const op_table_entry *entry;
    unsigned char *tag = res->tag;

    if (!xdr_u_int32_t(xdr, &res->status))
        return FALSE;

    if (!xdr_bytes(xdr, (char **)&tag, &res->tag_len, NFS4_OPAQUE_LIMIT))
        return FALSE;

    expected_count = res->resarray_count;
    if (!xdr_u_int32_t(xdr, &res->resarray_count))
        return FALSE;

    /* validate the number of operations against what we sent */
    if (res->resarray_count > expected_count) {
        eprintf("reply with %u operations, only sent %u!\n",
            res->resarray_count, expected_count);
        return FALSE;
    } else if (res->resarray_count < expected_count &&
        res->status == NFS4_OK) {
        /* illegal for a server to reply with less operations,
         * unless one of them fails */
        eprintf("successful reply with only %u operations, sent %u!\n",
            res->resarray_count, expected_count);
        return FALSE;
    }

    for (i = 0; i < res->resarray_count; i++)
    {
        expected_op = res->resarray[i].op;
        if (!xdr_u_int32_t(xdr, &res->resarray[i].op))
            return FALSE;

        /* validate each operation number against what we sent */
        if (res->resarray[i].op != expected_op) {
            eprintf("reply with %s in operation %u, expected %s!\n",
                nfs_opnum_to_string(res->resarray[i].op), i+1,
                nfs_opnum_to_string(expected_op));
            return FALSE;
        }

        entry = op_table_find(res->resarray[i].op);
        if (entry == NULL || entry->decode == NULL)
            return FALSE;
        if (!entry->decode(xdr, &res->resarray[i]))
            return FALSE;
    }
    return TRUE;
}
