dg.c   [plain text]


/*
 * Copyright (c) 2010 Apple Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Inc. ("Apple") nor the names of its
 *     contributors may be used to endorse or promote products derived from
 *     this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Portions of this software have been released under the following terms:
 *
 * (c) Copyright 1989-1993 OPEN SOFTWARE FOUNDATION, INC.
 * (c) Copyright 1989-1993 HEWLETT-PACKARD COMPANY
 * (c) Copyright 1989-1993 DIGITAL EQUIPMENT CORPORATION
 *
 * To anyone who acknowledges that this file is provided "AS IS"
 * without any express or implied warranty:
 * permission to use, copy, modify, and distribute this file for any
 * purpose is hereby granted without fee, provided that the above
 * copyright notices and this notice appears in all source code copies,
 * and that none of the names of Open Software Foundation, Inc., Hewlett-
 * Packard Company or Digital Equipment Corporation be used
 * in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.  Neither Open Software
 * Foundation, Inc., Hewlett-Packard Company nor Digital
 * Equipment Corporation makes any representations about the suitability
 * of this software for any purpose.
 *
 * Copyright (c) 2007, Novell, Inc. All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Novell Inc. nor the names of its contributors
 *     may be used to endorse or promote products derived from this
 *     this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @APPLE_LICENSE_HEADER_END@
 */

/*
**
**  NAME:
**
**      dg.c
**
**  FACILITY:
**
**      Remote Procedure Call (RPC)
**
**  ABSTRACT:
**
**  DG protocol service routines.  Most entries points from common services are
**  in this module.
**
**
*/

#include <dg.h>
#include <dgrq.h>
#include <dgxq.h>
#include <dgpkt.h>
#include <dgcall.h>
#include <dgccall.h>
#include <dgccallt.h>
#include <dgcct.h>

/* =============================================================================== */

/*
 * Define a fragment number based function that determines if it is time
 * to check for (and fwd) a pending cancel for a call.
 *
 * We don't have to check too frequently.  However we also don't want
 * to do it too infrequently since the cancel timeout timer won't get
 * started until one is detected.
 */
#ifndef RPC_DG_CANCEL_CHECK_FREQ
#  define RPC_DG_CANCEL_CHECK_FREQ    64
#endif

#ifndef _PTHREAD_NO_CANCEL_SUPPORT
#    define CANCEL_CHECK(fnum, call) \
    if ((fnum) >= RPC_DG_CANCEL_CHECK_FREQ && \
        (fnum) % RPC_DG_CANCEL_CHECK_FREQ == 0) \
    { \
        cancel_check(call); \
    }

    INTERNAL void cancel_check ( rpc_dg_call_p_t );

#else
#    define CANCEL_CHECK(fnum, call)  {}
#endif

/* =================================================================== */

/*
 * C A N C E L _ C H E C K
 *
 * Check for a posted cancel; if there was one, handle it.  This routine
 * must flush the posted cancel.  Of course, if general cancel delivery
 * is disabled, then we aren't required (and in fact don't want) to detect
 * one.
 *
 * For ccalls, Once a cancel timeout has been established there's really
 * no need for us to continue to check;  a cancel has been forwarded
 * and either the call will complete before the timeout occurs or not.
 * Scalls also need to check for cancels (see rpc__dg_call_local_cancel).
 */

#ifndef _PTHREAD_NO_CANCEL_SUPPORT
INTERNAL void cancel_check(call)
rpc_dg_call_p_t call;
{
    RPC_DG_CALL_LOCK_ASSERT(call);

    if (RPC_DG_CALL_IS_CLIENT(call))
    {
        rpc_dg_ccall_p_t ccall = (rpc_dg_ccall_p_t) call;

        if (ccall->cancel.timeout_time > 0)
        {
            return;
        }
    }

    DCETHREAD_TRY
    {
        dcethread_checkinterrupt();
    }
    DCETHREAD_CATCH_ALL(THIS_CATCH)
    {
        RPC_DBG_PRINTF(rpc_e_dbg_cancel, 10,
            ("(check_and_fwd) local cancel detected\n"));
        rpc__dg_call_local_cancel(call);
    }
    DCETHREAD_ENDTRY
}
#endif

/*
 * R P C _ _ D G _ C A L L _ T R A N S M I T _ I N T
 *
 * Send part of RPC call request/response and return.
 *
 * Note: a 0 length transmit request is legitimate and typically causes an
 * empty request fragment to get created and queued.  This is how RPCs with
 * 0 ins get a request fragment created.  We don't special case this
 * processing, it happens naturally given the way this code is written
 * (which also means that we don't bother ensuring that this is the only
 * situation where a 0 length request fragment can be generated - that's
 * the stub's responsibility).
 *
 * Since a sender may have the good fortune to virtually never block,
 * we must periodically forceable check for a pending cancel.  We don't have
 * to do this too frequently.  However we also don't want to do it too
 * infrequently since the cancel timeout timer won't get started until one
 * is detected.  Once a cancel timeout has been established there's really no
 * need for us to check.
 */

PRIVATE void rpc__dg_call_transmit_int(call, data, st)
rpc_dg_call_p_t call;
rpc_iovector_p_t data;
unsigned32 *st;
{
    unsigned16 room_left;               /* # bytes left in partial packet body */
    unsigned16 frag_length = 0;         /* # bytes in the entire frag */
    boolean    alloc_pkt;

    unsigned16 elt_num;                 /* Current data elt being processed */
    unsigned32 data_used;               /* # bytes of current data elt consumed */
    size_t body_size = 0;
    rpc_dg_xmitq_p_t xq = &call->xq;
    rpc_dg_xmitq_elt_p_t xqe = NULL;

    boolean end_of_data;
    rpc_dg_auth_epv_p_t auth_epv = call->auth_epv;

    RPC_DG_CALL_LOCK_ASSERT(call);

    /*
     * Check for call failure conditions...
     */
    *st = call->status;
    if (*st != rpc_s_ok)
        return;

    if (call->state == rpc_e_dg_cs_orphan)
    {
        if (xq->head != NULL)
            rpc__dg_xmitq_free(xq, call);
        *st = rpc_s_call_orphaned;
        return;
    }

    if (RPC_DG_CALL_IS_SERVER(call) && call->state == rpc_e_dg_cs_recv)
    {
        /*
         * This is a turnaround. The server, which has been the
         * receiver, becomes the sender.
         *
         * Instead of sending a small fragment, we start with the largest
         * fragment size seen so far. We assume that the client can receive
         * the fragment as large as what it has been sending; since the
         * client's xq.snd_frag_size is bounded by its xq.max_frag_size.
         *
         * Thanks for the reservation we have made so far, we have enough
         * reservations to send this large fragment!
         */

        if (call->rq.high_rcv_frag_size > xq->snd_frag_size)
        {
            xq->snd_frag_size = MIN(call->rq.high_rcv_frag_size,
                                    xq->max_snd_tsdu);
            /*
             * Fragment sizes must be 0 MOD 8.  Make it so.
             */
            xq->snd_frag_size &= ~ 0x7;

            /*
             * If we are going to use high_rcv_frag_size, then it implies that
             * we don't need to wait for the first fack.
             */
            xq->first_fack_seen = true;
        }

        /*
         * For KRPC, should we reduce the packet reservation here?
         */
    }

    elt_num = 0;
    data_used = 0;
    end_of_data = data->num_elt == 0 ? true : false;

    /*
     * Since we don't retain the computed body size (maybe we should?)
     * we need to recompute it if we've already got a partial pkt.
     */
    if (xq->part_xqe != NULL)
    {
        body_size = MIN(xq->snd_frag_size, RPC_C_DG_MAX_PKT_BODY_SIZE);

        xqe = xq->part_xqe;
        frag_length = xqe->body_len + RPC_C_DG_RAW_PKT_HDR_SIZE;
        while (xqe->more_data)
        {
            xqe = xqe->more_data;
            frag_length += xqe->body_len;
        }

        if (auth_epv != NULL)
            frag_length += (auth_epv->overhead);
    }

    do
    {
        /*
         * Broadcasts are not allowed to send fragments.  If the broadcast
         * attribute is set, check to see if we already have a full packet
         * of data, and are beginning to fill a second.
         */

        if (((xq->base_flags & RPC_C_DG_PF_BROADCAST) != 0) &&
            (xq->next_fragnum > 0))
        {
            *st = rpc_s_in_args_too_big;
            return;
        }

        /*
         * Make sure we have a partial packet to copy into.  If we don't
         * have one, first wait until there's some window space open
         * (if necessary) and then allocate a partial packet.  Make
         * sure each pkt gets filled to the max allowable capacity.
         * If the call has an error associated with it, we're done.
         */

        if (xq->part_xqe == NULL)
        {
            frag_length = RPC_C_DG_RAW_PKT_HDR_SIZE;
            alloc_pkt = true;
            if (auth_epv != NULL)
                frag_length += (auth_epv->overhead);
        }
        else
        {
            assert(xqe != NULL);
            alloc_pkt = (xqe->body_len == body_size) &&
                        (frag_length < xq->snd_frag_size);
        }

        if (alloc_pkt)
        {
            rpc_dg_xmitq_elt_p_t tmp;
            /*
             * Sleep until
             *    1) a fack comes in and
             *          a) opens window space
             *          b) packets need to be rexmitted
             *    2) a timeout occurs and packets need to be rexmitted.
             *    3) the first fack comes in to tell the receiver's idea
             *       of the right fragment size, iff non-callback.
             *
             * Either way, call call_xmit to handle the new queue state.
             */
            while ((xq->first_unsent != NULL &&
                    RPC_DG_FRAGNUM_IS_LTE(xq->first_unsent->fragnum +
                        xq->max_blast_size, xq->next_fragnum))
                   || (xq->next_fragnum == 1 &&
                       xq->first_fack_seen == false &&
                       !call->is_cbk))
            {
                rpc__dg_call_wait(call, rpc_e_dg_wait_on_network_event, st);
                if (*st != rpc_s_ok)
                    return;
            }

            tmp = rpc__dg_pkt_alloc_xqe(call, st);
            if (*st != rpc_s_ok)
                return;

            if (xq->part_xqe == NULL)
                xqe = xq->part_xqe = tmp;
            else
            {
                assert(xqe != NULL);
                xqe->more_data = tmp;
                xqe = tmp;
            }

            body_size = MIN(xq->snd_frag_size, RPC_C_DG_MAX_PKT_BODY_SIZE);
        }

        /*
         * Copy as much data into the partial packet as will fit.
         */

        room_left = MIN(body_size - xqe->body_len,
                        xq->snd_frag_size - frag_length);

        if (elt_num < data->num_elt) {
            rpc_iovector_elt_t *iove = &data->elt[elt_num];

            while (true) {
                unsigned32 data_left = iove->data_len - data_used;

                if (room_left <= data_left) {
                    memcpy(((char *)xqe->body)+xqe->body_len, &iove->data_addr[data_used],
                          room_left);
                    data_used += room_left;
                    xqe->body_len += room_left;
                    frag_length += room_left;

                    /*
                     * If we copied the full data buffer, and it's the last one,
                     * set end of data flag, and free the buffer.
                     */

                    if (data_used == iove->data_len && (elt_num + 1 == data->num_elt))
                    {
                        end_of_data = true;

                        if (iove->buff_dealloc != NULL)
                            RPC_FREE_IOVE_BUFFER(iove);
                    }
                    break;
                }
                else {
                    memcpy(((char *)xqe->body)+xqe->body_len, &iove->data_addr[data_used],
                          data_left);
                    room_left -= data_left;
                    xqe->body_len += data_left;
                    frag_length += data_left;
                    if (iove->buff_dealloc != NULL)
                        RPC_FREE_IOVE_BUFFER(iove);

                    /*
                     * We copied the full data buffer;  if it's the last one,
                     * set end of data flag.
                     */

                    if (++elt_num == data->num_elt)
                    {
                        end_of_data = true;
                        break;
                    }
                    iove = &data->elt[elt_num];
                    data_used = 0;
                }
            }
        }

        RPC_DG_CALL_SET_STATE(call, rpc_e_dg_cs_xmit);

        /*
         * The transmit/windowing routines assume that anything on the
         * queue is ready to be sent, so we need to worry that we don't
         * put the last packet on the queue here (or the last_frag bit
         * won't get set).  If we have run out of new data, leave the
         * partial packet as it is (possibly full), and return.
         */

        if (end_of_data)
            return;

        /*
         * Add newly-filled pkt to xmit queue.
         */

        if (frag_length == xq->snd_frag_size)
        {
            rpc__dg_xmitq_append_pp(call, st);
            if (*st != rpc_s_ok)
                return;

            /*
             * When (re)starting a window, we can't wait for an ack/timeout
             * before sending data. In this case (only) for the xmit by setting
             * the blast size, and calling call_xmit() directly.
             *
             * Instead of starting the window with the one fragment queued,
             * send two fragments since we have the max pkt reservation.
             */

            if (xq->freqs_out == 0)
            {
                if (call->n_resvs < 2)
                {
                    xq->blast_size = 1;
                    rpc__dg_call_xmit(call, true);
                }
                else if (xq->head != xq->tail)
                {
                    xq->blast_size = 2;
                    rpc__dg_call_xmit(call, true);
                }
            }

            /*
             * periodically check for a local posted cancel...
             */
            CANCEL_CHECK(xq->next_fragnum, call);
        }
    }
    while (elt_num < data->num_elt);
}

/*
 * R P C _ _ D G _ C A L L _ R E C E I V E _ I N T
 *
 * Return the next, and possibly last part of the RPC call
 * request/response.
 *
 * Note well that this routine differs from "rpc__dg_call_receive" in
 * that this routine might return an I/O vector element whose data length
 * is zero but whose dealloc routine pointer is non-NULL.  Callers of
 * this routine must call the dealloc routine even in the case of
 * zero-length data.
 *
 * Since a receiver may have the good fortune to virtually never block,
 * we must periodically forceable check for a pending cancel.
 */

PRIVATE void rpc__dg_call_receive_int(call, data, st)
rpc_dg_call_p_t call;
rpc_iovector_elt_t *data;
unsigned32 *st;
{
    rpc_dg_recvq_p_t rq = &call->rq;
    rpc_dg_recvq_elt_p_t rqe;
    rpc_key_info_p_t key_info = call->key_info;

    RPC_DG_CALL_LOCK_ASSERT(call);

    data->data_len = 0;
    data->data_addr = NULL;
    data->buff_dealloc = NULL;
    data->buff_len = 0;
    data->buff_addr = NULL;

    /*
     * Check for call failure conditions...
     */
    *st = call->status;
    if (*st != rpc_s_ok)
        return;

    if (call->state == rpc_e_dg_cs_orphan)
    {
        if (rq->head != NULL)
            rpc__dg_recvq_free(rq);
        *st = rpc_s_call_orphaned;
        return;
    }

    /*
     * Check to see if there's any more receive data to return.
     * The above call->status check ensures that we will return
     * EOD in non-call-failure conditions.
     */
    if (rq->all_pkts_recvd && rq->head == NULL)
        return;

    /*
     * Wait till the next in-order fragment is available.
     * If the call has an error associated with it, we're done.
     */

    while (rq->last_inorder == NULL)
    {
        rpc__dg_call_wait(call, rpc_e_dg_wait_on_network_event, st);

        if (*st != rpc_s_ok)
            return;
    }

    rqe = rq->head;

    /*
     * Check authentication iff the head of the fragment.
     */
    if (key_info != NULL && rqe->hdrp != NULL)
    {
        rpc_dg_auth_epv_p_t auth_epv = call->auth_epv;

        unsigned32 blocksize = auth_epv->blocksize;
        char *cksum;
        unsigned32 raw_bodysize;
        unsigned32 pkt_bodysize;
        rpc_dg_recvq_elt_p_t last_rqe = rqe;
        char auth_trailer[32];  /* XXX: should be malloc'ed */
        byte_p_t real_auth_trailer = NULL;
        unsigned32 saved_pkt_len = 0;

        /*
         * It's not really necessary to round up the packet body
         * length here because the sender includes the length of
         * padding before the auth trailer in the packet body length.
         * However, I think, that's a wrong behavior and we shouldn't
         * rely on it.
         */
        raw_bodysize = ((rqe->hdrp->len + blocksize - 1)
                        / blocksize) * blocksize;

        /*
         * Verify that cksum is entirely contained inside the packet,
         * and the auth_type is what we expected.
         *
         * This "shouldn't fail" unless someone's playing games with
         * us.
         */

        if (((RPC_C_DG_RAW_PKT_HDR_SIZE + raw_bodysize + auth_epv->overhead)
            > rqe->frag_len) ||
            (rqe->hdrp->auth_proto != auth_epv->auth_proto))
        {
            *st = nca_s_proto_error;
        }
        else
        {
            pkt_bodysize = MIN(raw_bodysize,
                               last_rqe->pkt_len - RPC_C_DG_RAW_PKT_HDR_SIZE);
            raw_bodysize -= pkt_bodysize;

            while (last_rqe->more_data != NULL && raw_bodysize > 0)
            {
                pkt_bodysize = MIN(raw_bodysize,
                                   last_rqe->more_data->pkt_len);
                raw_bodysize -= pkt_bodysize;
                last_rqe = last_rqe->more_data;
            }
            cksum = last_rqe->pkt->body.args + pkt_bodysize;

            if (pkt_bodysize + auth_epv->overhead >
                ((rqe == last_rqe) ?
                 (last_rqe->pkt_len - RPC_C_DG_RAW_PKT_HDR_SIZE) :
                 (last_rqe->pkt_len)))
            {
                /*
                 * Heck, auth trailer is not continuous!
                 * or the *real* last rqe has auth trailer only.
                 */
                unsigned32 len;

                assert(last_rqe->more_data != NULL);

                if (rqe == last_rqe)
                    len = last_rqe->pkt_len -
                        (pkt_bodysize + RPC_C_DG_RAW_PKT_HDR_SIZE);
                else
                    len = last_rqe->pkt_len - pkt_bodysize;

                /*
                 * We should always allocate auth_trailer.
                 * But, for now...
                 */
                if (auth_epv->overhead <= 24)
                {
                    cksum = (char *)RPC_DG_ALIGN_8(auth_trailer);
                }
                else
                {
                    RPC_MEM_ALLOC (real_auth_trailer,
                                   byte_p_t,
                                   auth_epv->overhead + 7,
                                   RPC_C_MEM_UTIL,
                                   RPC_C_MEM_WAITOK);
                    if (real_auth_trailer == NULL)
                    {
                        *st = rpc_s_no_memory;
                        goto after_recv_ck;
                    }
                    cksum = (char *)RPC_DG_ALIGN_8(real_auth_trailer);
                }
                memcpy(cksum, last_rqe->pkt->body.args + pkt_bodysize, len);
                memcpy(cksum + len, last_rqe->more_data->pkt->body.args,
                       auth_epv->overhead - len);
            }

            /*
             * Adjust the last packet buffer's pkt_len,
             * i.e., excluding the auth trailer.
             */
            if (rqe == last_rqe)
            {
                saved_pkt_len = last_rqe->pkt_len;
                last_rqe->pkt_len = pkt_bodysize + RPC_C_DG_RAW_PKT_HDR_SIZE;
            }
            else
            {
                saved_pkt_len = last_rqe->pkt_len;
                last_rqe->pkt_len = pkt_bodysize;
            }
            (*auth_epv->recv_ck) (key_info, rqe, cksum, st);

            if (real_auth_trailer != NULL)
                RPC_MEM_FREE (real_auth_trailer, RPC_C_MEM_UTIL);
        }
    after_recv_ck:
        if (*st == rpc_s_dg_need_way_auth)
        {
            /*
             * Restore the last packet buffer's pkt_len.
             */
            if (saved_pkt_len != 0)
                last_rqe->pkt_len = saved_pkt_len;

            /*
             * Setup the return iovector element.
             *
             * Note: The call executor does not own this fragment and should
             * never free it. It's supposed to call me again.
             */
            RPC_DG_RECVQ_IOVECTOR_SETUP(data, rqe);
            return;
        }
        else if (*st != rpc_s_ok)
            return;

        /*
         * The *real* last rqe is no longer necessary.
         */
        if (last_rqe->more_data != NULL)
        {
            rpc__dg_pkt_free_rqe(last_rqe->more_data, call);
            last_rqe->more_data = NULL;
        }
    }

    /*
     * Dequeue the fragment off the head of the list.  Adjust the last
     * in-order pointers if necessary.
     */

    if (rqe->hdrp != NULL)
    {
        rq->head_serial_num =
            (rqe->hdrp->serial_hi << 8) | rqe->hdrp->serial_lo;
    }
    if (rqe->more_data != NULL)
    {
        rq->head = rqe->more_data;
        rq->head->next = rqe->next;
        if (rq->last_inorder == rqe)
            rq->last_inorder = rq->head;
        rqe->more_data = NULL;
    }
    else
    {
        rq->head = rqe->next;
        if (rq->head != NULL)
            rq->head_fragnum = rq->head->hdrp->fragnum;
        if (rq->last_inorder == rqe)
            rq->last_inorder = NULL;
        rq->inorder_len--;
        rq->queue_len--;
    }

    /*
     * If the queue was completely full, the sender will have been sent
     * a "0 window size" fack to stop it from sending.  Send a fack now
     * to let it know it can begin sending again.
     */
    if (rq->queue_len == rq->max_queue_len - 1)
    {
        RPC_DBG_PRINTF(rpc_e_dbg_recv, 1,
            ("(rpc__dg_call_receive_int) sending fack to prod peer\n"));
        rpc__dg_call_xmit_fack(call, rqe, false);
    }

    /*
     * periodically check for a local posted cancel...
     */
    if (rqe->hdrp != NULL)
    CANCEL_CHECK(rqe->hdrp->fragnum, call);

    *st = rpc_s_ok;

    /*
     * Setup the return iovector element.  Note, the stubs are obligated
     * to pass us a non-null data even if they "know" there are no outs
     * (which means that we must initialize it).  Since someone has to
     * free the rqe (and the stub always has to check), we keep the
     * code path simpler and just let them always do it.
     */

    RPC_DG_RECVQ_IOVECTOR_SETUP(data, rqe);
}

/*
 * R P C _ _ D G _ C A L L _ T R A N S M I T
 *
 * Shell over internal "comm_transmit" routine that acquires the call lock
 * first.
 */

PRIVATE void rpc__dg_call_transmit(call_, data, st)
rpc_call_rep_p_t call_;
rpc_iovector_p_t data;
unsigned32 *st;
{
    rpc_dg_call_p_t call = (rpc_dg_call_p_t) call_;

    if (RPC_DG_CALL_IS_CLIENT(call)) {
        assert(call->state == rpc_e_dg_cs_init || call->state == rpc_e_dg_cs_xmit);
    }
    else
    {
        assert(call->state == rpc_e_dg_cs_recv || call->state == rpc_e_dg_cs_xmit || call->state == rpc_e_dg_cs_orphan);
    }

    RPC_DG_CALL_LOCK(call);
    rpc__dg_call_transmit_int(call, data, st);
    RPC_DG_CALL_UNLOCK(call);
}

/*
 * R P C _ _ D G _ C A L L _ R E C E I V E
 *
 * Shell over internal "comm_receive" routine that acquires the call lock
 * first.
 */

PRIVATE void rpc__dg_call_receive(call_, data, st)
rpc_call_rep_p_t call_;
rpc_iovector_elt_t *data;
unsigned32 *st;
{
    rpc_dg_call_p_t call = (rpc_dg_call_p_t) call_;

    assert(call->state == rpc_e_dg_cs_recv || call->state == rpc_e_dg_cs_orphan);

    RPC_DG_CALL_LOCK(call);

    rpc__dg_call_receive_int(call, data, st);

    if (*st == rpc_s_ok)
    {
        if (data->data_len == 0 && data->buff_dealloc != NULL)
            RPC_FREE_IOVE_BUFFER(data);
    }

    RPC_DG_CALL_UNLOCK(call);
}

/*
 * R P C _ _ D G _ C A L L _ T R A N S C E I V E
 *
 * Send last of RPC call request, wait for RPC call response, and return
 * at least the first part of it along with its associated drep.
 */

PRIVATE void rpc__dg_call_transceive(call_, xmit_data, recv_data, ndr_format, st)
rpc_call_rep_p_t call_;
rpc_iovector_p_t xmit_data;
rpc_iovector_elt_t *recv_data;
ndr_format_t *ndr_format;
unsigned32 *st;
{
    rpc_dg_ccall_p_t ccall;
    rpc_dg_binding_client_p_t h;

    ccall = (rpc_dg_ccall_p_t) call_;
    assert(RPC_DG_CALL_IS_CLIENT(&ccall->c));

    assert(ccall->c.state == rpc_e_dg_cs_init || ccall->c.state == rpc_e_dg_cs_xmit);

    RPC_DG_CALL_LOCK(&ccall->c);

    /*
     * Be sure to handle the no-IN's case (generate at least one request
     * packet even if there are no IN's for this remote call).  Also,
     * if there is input data to this call, push that out.  (We're trying
     * to avoid the case where data has been sent previously but then
     * transceive is called with no transmit data.)
     */

    if (ccall->c.state == rpc_e_dg_cs_init || xmit_data->num_elt > 0)
    {
        rpc__dg_call_transmit_int(&ccall->c, xmit_data, st);

        if (*st != rpc_s_ok)
        {
            RPC_DG_CALL_UNLOCK(&ccall->c);
            return;
        }
    }

    rpc__dg_call_xmitq_push(&ccall->c, st);
    if (*st != rpc_s_ok)
    {
        RPC_DG_CALL_UNLOCK(&ccall->c);
        return;
    }

    /*
     * If this is a "maybe" call, there's no response and we're done.
     * Otherwise, wait for and return the 1st response fragment.
     */

    if (ccall->c.xq.base_flags & RPC_C_DG_PF_MAYBE)
    {
        recv_data->data_len = 0;
        recv_data->buff_dealloc = NULL;
        recv_data->buff_addr = NULL;
        ccall->c.rq.all_pkts_recvd = true;
    }
    else
    {
        RPC_DG_PING_INFO_INIT(&ccall->ping);
        RPC_DG_CALL_SET_STATE(&ccall->c, rpc_e_dg_cs_recv);

        rpc__dg_call_receive_int(&ccall->c, recv_data, st);

        if (*st == rpc_s_ok)
        {
            rpc_dg_recvq_elt_p_t rqe =
                RPC_DG_RECVQ_ELT_FROM_IOVECTOR_ELT(recv_data);
            /*
             * The first rqe must have a valid hdrp.
             */
            assert(rqe->hdrp != NULL);
            RPC_DG_HDR_INQ_DREP(ndr_format, rqe->hdrp);

            if (recv_data->data_len == 0 && recv_data->buff_dealloc != NULL)
                RPC_FREE_IOVE_BUFFER(recv_data);
        }

        /*
         * Update the binding handle with bound server instance information.
         * Only do this if the call succeded in binding to a server (which
         * may not always be the case; e.g. consider "maybe" calls).  Signal
         * other threads that that may have been blocked for binding
         * serialization.  Watch out for the locking hierarchy violation.
         */

        h = (rpc_dg_binding_client_p_t) ccall->h;
        if (! h->c.c.bound_server_instance && ccall->server_bound)
        {
            boolean gotit;
            unsigned32 tst;

            RPC_TRY_LOCK(&gotit);
            if (! gotit)
            {
                RPC_DG_CALL_UNLOCK(&ccall->c);
                RPC_LOCK(0);
                RPC_DG_CALL_LOCK(&ccall->c);
            }

            h->server_boot = ccall->c.call_server_boot;
            rpc__naf_addr_overcopy(ccall->c.addr, &h->c.c.rpc_addr, &tst);
            h->c.c.addr_has_endpoint = true;
            h->c.c.bound_server_instance = true;
            RPC_DBG_PRINTF(rpc_e_dbg_general, 5,
                ("(rpc__dg_call_transceive) unblocking serialized waiters...\n"));
            RPC_BINDING_COND_BROADCAST(0);

            RPC_UNLOCK(0);
        }
    }

    RPC_DG_CALL_UNLOCK(&ccall->c);
}

/*
 * R P C _ _ D G _ C A L L _ B L O C K _ U N T I L _ F R E E
 *
 * Response completely sent. Block until memory associated with reponse
 * is free.
 */
PRIVATE void rpc__dg_call_block_until_free(call_, st)
rpc_call_rep_p_t call_ ATTRIBUTE_UNUSED;
unsigned32 *st;
{
    assert(RPC_DG_CALL_IS_SERVER((rpc_dg_call_p_t) call_));

    *st = rpc_s_ok;

    /*
     * This is currently a NO-OP because we are always copying the
     * data provided to us by the stub.
     */
}

/*
 * R P C _ _ D G _ C A L L _ A L E R T
 *
 * Alert current RPC call.
 */
PRIVATE void rpc__dg_call_alert(call_, st)
rpc_call_rep_p_t call_ ATTRIBUTE_UNUSED;
unsigned32 *st;
{
    *st = rpc_s_ok;

    /*
     * rpc_m_unimp_call
     * "(%s) Call not implemented"
     */
    rpc_dce_svc_printf (
        __FILE__, __LINE__,
        "%s",
        rpc_svc_general,
        svc_c_sev_fatal | svc_c_action_abort,
        rpc_m_unimp_call,
        "rpc__dg_call_alert" );
}

/*
 * R P C _ _ D G _ C A L L _ R E C E I V E _ F A U L T
 *
 */

PRIVATE void rpc__dg_call_receive_fault(call_, data, ndr_format, st)
rpc_call_rep_p_t call_;
rpc_iovector_elt_t *data;
ndr_format_t *ndr_format;
unsigned32 *st;
{
    rpc_dg_ccall_p_t ccall;
    rpc_key_info_p_t key_info;
    rpc_dg_recvq_elt_p_t rqe;

    ccall = (rpc_dg_ccall_p_t) call_;
    assert(RPC_DG_CALL_IS_CLIENT(&ccall->c));
    if (ccall->fault_rqe == NULL)
    {
        *st = rpc_s_no_fault;
        return;
    }
    rqe = ccall->fault_rqe;

    *st = rpc_s_ok;

    RPC_DG_CALL_LOCK(&ccall->c);

    data->data_len = 0;
    data->data_addr = NULL;
    data->buff_dealloc = NULL;
    data->buff_len = 0;
    data->buff_addr = NULL;

    /*
     * Check authentication.
     */
    key_info = ccall->c.key_info;

    /*
     * Check authentication iff the head of the fragment.
     */
    if (key_info != NULL && rqe->hdrp != NULL)
    {
        rpc_dg_auth_epv_p_t auth_epv = ccall->c.auth_epv;

        unsigned32 blocksize = auth_epv->blocksize;
        char *cksum;
        unsigned32 raw_bodysize;
        unsigned32 pkt_bodysize;
        rpc_dg_recvq_elt_p_t last_rqe = rqe;
        char auth_trailer[32];  /* XXX: should be malloc'ed */
        byte_p_t real_auth_trailer = NULL;

        /*
         * It's not really necessary to round up the packet body
         * length here because the sender includes the length of
         * padding before the auth trailer in the packet body length.
         * However, I think, that's a wrong behavior and we shouldn't
         * rely on it.
         */
        raw_bodysize = ((rqe->hdrp->len + blocksize - 1)
                        / blocksize) * blocksize;

        /*
         * Verify that cksum is entirely contained inside the packet,
         * and the auth_type is what we expected.
         *
         * This "shouldn't fail" unless someone's playing games with
         * us.
         */

        if (((RPC_C_DG_RAW_PKT_HDR_SIZE + raw_bodysize + auth_epv->overhead)
            > rqe->frag_len) ||
            (rqe->hdrp->auth_proto != auth_epv->auth_proto))
        {
            *st = nca_s_proto_error;
        }
        else
        {
            pkt_bodysize = MIN(raw_bodysize,
                               last_rqe->pkt_len - RPC_C_DG_RAW_PKT_HDR_SIZE);
            raw_bodysize -= pkt_bodysize;

            while (last_rqe->more_data != NULL && raw_bodysize > 0)
            {
                pkt_bodysize = MIN(raw_bodysize,
                                   last_rqe->more_data->pkt_len);
                raw_bodysize -= pkt_bodysize;
                last_rqe = last_rqe->more_data;
            }
            cksum = last_rqe->pkt->body.args + pkt_bodysize;

            if (pkt_bodysize + auth_epv->overhead >
                ((rqe == last_rqe) ?
                 (last_rqe->pkt_len - RPC_C_DG_RAW_PKT_HDR_SIZE) :
                 (last_rqe->pkt_len)))
            {
                /*
                 * Heck, auth trailer is not continuous!
                 * or the *real* last rqe has auth trailer only.
                 */
                unsigned32 len;

                assert(last_rqe->more_data != NULL);

                if (rqe == last_rqe)
                    len = last_rqe->pkt_len -
                        (pkt_bodysize + RPC_C_DG_RAW_PKT_HDR_SIZE);
                else
                    len = last_rqe->pkt_len - pkt_bodysize;

                /*
                 * We should allocate auth_trailer.
                 * But, for now...
                 */
                if (auth_epv->overhead <= 24)
                {
                    cksum = (char *)RPC_DG_ALIGN_8(auth_trailer);
                }
                else
                {
                    RPC_MEM_ALLOC (real_auth_trailer,
                                   byte_p_t,
                                   auth_epv->overhead + 7,
                                   RPC_C_MEM_UTIL,
                                   RPC_C_MEM_WAITOK);
                    if (real_auth_trailer == NULL)
                    {
                        *st = rpc_s_no_memory;
                        goto after_recv_check;
                    }
                    cksum = (char *)RPC_DG_ALIGN_8(real_auth_trailer);
                }
                memcpy(cksum, last_rqe->pkt->body.args + pkt_bodysize, len);
                memcpy(cksum + len, last_rqe->more_data->pkt->body.args,
                       auth_epv->overhead - len);

                /*
                 * The *real* last rqe is no longer necessary.
                 */
                rpc__dg_pkt_free_rqe(last_rqe->more_data, &ccall->c);
                last_rqe->more_data = NULL;
            }

            /*
             * Adjust the last packet buffer's pkt_len,
             * i.e., excluding the auth trailer.
             */
            if (rqe == last_rqe)
                last_rqe->pkt_len = pkt_bodysize + RPC_C_DG_RAW_PKT_HDR_SIZE;
            else
                last_rqe->pkt_len = pkt_bodysize;
            (*auth_epv->recv_ck) (key_info, rqe, cksum, st);

            if (real_auth_trailer != NULL)
                RPC_MEM_FREE (real_auth_trailer, RPC_C_MEM_UTIL);
        }
    after_recv_check:
        if (*st != rpc_s_ok)
        {
            rpc__dg_pkt_free_rqe(rqe, &ccall->c);
            ccall->fault_rqe = NULL;    /* we no longer own the rqe */
            RPC_DG_CALL_UNLOCK(&ccall->c);
            return;
        }
    }

     /*
      * The multi-buffer fault fragment is not officially supported and
      * the IDL generated stubs won't use it because the fault pdu
      * contains only unsigned32 status. Well, maybe someone can use
      * it...
      */
    if (rqe->more_data != NULL)
    {
        ccall->fault_rqe = rqe->more_data;
        ccall->fault_rqe->next = rqe->next; /* should be NULL */
        rqe->more_data = NULL;
    }
    else
        ccall->fault_rqe = NULL;

    RPC_DG_RECVQ_IOVECTOR_SETUP(data, rqe);
    if (rqe->hdrp != NULL)
        RPC_DG_HDR_INQ_DREP(ndr_format, rqe->hdrp);

    RPC_DG_CALL_UNLOCK(&ccall->c);
}

/*
 * R P C _ _ D G _ C A L L _ F A U L T
 *
 * Declare the current call as "faulted", generating a fault packet with
 * the provided body.
 *
 * Server ONLY.
 */
PRIVATE void rpc__dg_call_fault(call_, fault_info, st)
rpc_call_rep_p_t call_;
rpc_iovector_p_t fault_info;
unsigned32 *st;
{
    rpc_dg_scall_p_t scall = (rpc_dg_scall_p_t) call_;
    unsigned32 tst;

    assert(RPC_DG_CALL_IS_SERVER(&scall->c));

    *st = rpc_s_ok;

    RPC_DG_CALL_LOCK(&scall->c);

    /*
     * Purge the recvq since it won't be used after this.  The recvq
     * may currently have lots of rqes on it and freeing it now will
     * help pkt quotas.
     */

    rpc__dg_recvq_free(&scall->c.rq);

    /*
     * Toss any pending xmitq pkts and add the fault_info to the xmit
     * queue just as if it were a response (but whack the proto pkt header
     * to the fault pkt type).  The call will now be in the xmit state if it
     * wasn't already there.  Defer the sending of the fault until
     * the "end of the call" (execute_call).  This prevents the client
     * from receiving the complete response, completing the call and
     * generating a new one while the server still thinks the call is
     * not complete (thinking it must have dropped an ack,...).  The
     * fault is really just a special response pkt.
     *
     * This routine is called by the sstub (the thread executing the
     * call) so there's no need to signal the call.  We don't actually
     * want the call's status to be set to a error value; the server
     * runtime wants to still complete processing the call which involves
     * sending the fault response to the client (instead of any further
     * data response).
     *
     * Subsequent fault response retransmissions will occur just as if
     * this were a "normal" call response as well as in reply to a ping.
     * Of course, faults for idempotent calls don't get remembered or
     * retransmitted.
     */

    RPC_DBG_GPRINTF(("(rpc__dg_call_fault) call has faulted [%s]\n",
        rpc__dg_act_seq_string(&scall->c.xq.hdr)));

    RPC_DG_XMITQ_REINIT(&scall->c.xq, &scall->c);
    RPC_DG_HDR_SET_PTYPE(&scall->c.xq.hdr, RPC_C_DG_PT_FAULT);

    rpc__dg_call_transmit_int(&scall->c, fault_info, &tst);

    RPC_DG_CALL_UNLOCK(&scall->c);
}

/*
 * R P C _ _ D G _ C A L L _ D I D _ M G R _ E X E C U T E
 *
 * Return whether or not the manager routine for the call identified
 * by the call handle has executed.
 */

PRIVATE boolean32 rpc__dg_call_did_mgr_execute(call_, st)
rpc_call_rep_p_t call_;
unsigned32 *st;
{
    rpc_dg_ccall_p_t ccall = (rpc_dg_ccall_p_t) call_;
    rpc_dg_xmitq_p_t xq = &ccall->c.xq;
    boolean r;

    *st = rpc_s_ok;

    RPC_DG_CALL_LOCK(&ccall->c);

    /*
     * If we don't know the boot time of the server and the call was not
     * idempotent, then the server CAN'T have executed the call.
     */
    if (ccall->c.call_server_boot == 0 &&
        ! RPC_DG_FLAG_IS_SET(xq->base_flags, RPC_C_DG_PF_IDEMPOTENT))
    {
        r = false;
        goto DONE;
    }

    /*
     * We decide whether the mgr executed as a function of any reject status
     * (i.e., from a "reject" packet).
     */
    switch ((int)ccall->reject_status)
    {
        /*
         * If ".reject_status" is zero, we didn't get a reject packet.  We
         * might have executed the manager.
         */
        case 0:
            r = true;
            goto DONE;

        /*
         * Any of these rejects means we KNOW we didn't execute the manager
         */
        case nca_s_unk_if:
        case nca_s_unsupported_type:
        case nca_s_manager_not_entered:
        case nca_s_op_rng_error:
        case nca_s_who_are_you_failed:
        case nca_s_wrong_boot_time:
            r = false;
            goto DONE;

        /*
         * Unknown reject status.  Assume the worst.
         */
        default:
            r = true;
            goto DONE;
    }

DONE:

    RPC_DG_CALL_UNLOCK(&ccall->c);
    return (r);
}

/*
 * R P C _ _ D G _ N E T W O R K _ I N Q _ P R O T _ V E R S
 *
 */

PRIVATE void rpc__dg_network_inq_prot_vers(prot_id, vers_major, vers_minor, st)
unsigned8 *prot_id;
unsigned32 *vers_major;
unsigned32 *vers_minor;
unsigned32 *st;
{
    *prot_id = RPC_C_DG_PROTO_ID;
    *vers_major = RPC_C_DG_PROTO_VERS;
    *vers_minor = 0;
    *st = rpc_s_ok;
}