/*<ATGCOPYRIGHT>
 * Copyright (C) 1997 Art Technology Group, Inc.
 * All Rights Reserved.  No use, copying or distribution of this
 * work may be made except in accordance with a valid license
 * agreement from Art Technology Group.  This notice must be
 * included on all copies, modifications and derivatives of this
 * work.
 *
 * Art Technology Group (ATG) MAKES NO REPRESENTATIONS OR WARRANTIES 
 * ABOUT THE SUITABILITY OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, 
 * INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. ATG SHALL NOT BE
 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, 
 * MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
 *
 * "Dynamo" is a trademark of Art Technology Group, Inc.
 </ATGCOPYRIGHT>*/

/*
  DRPClient.c - DRP client functionality
 */

/* #define DEBUG */


#ifdef WIN32
/* NT */
#include <winsock.h>

#define EINTR WSAEINTR
#define ETIMEDOUT WSAETIMEDOUT

#else
/* UNIXen */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/tcp.h>

#ifndef _DRP_MTHREAD
#  include <sys/timeb.h>
#endif

#define closesocket(s) close(s) /* to match NT closesocket() routine */
#endif


#include <stdio.h>
#include <string.h>
#include <memory.h>
#include <stdlib.h>


#include "DRPClient.h"
#include "DRPServerIO.h"

#include "CMLDRP.h"

#include "pageroute.h"

#include "rwLocks.h"

extern int errno;
extern DebugInfo gDebugInfo;

#ifdef _CPOOL
extern unsigned short gCP_Enabled;
#endif

/* The number of seconds that singly threaded web servers wait to poll the CM */
#define kSingleThreadPollSeconds 60

static time_t gLastUpdateTime = 0;

int gRetryServerAfterFailure = 0;

/* static globals */

#define kDefaultSessionCookieName "sessionid"

/**
 * This is the name used for both cookies and query arguments
 */
static char *gSessionCookieName = kDefaultSessionCookieName;
static int gSessionCookieNameLength = 9;

/* Name and port number of the server to redirect requests to in the case
 * of total dynamo failure.
 */
static char *gFailoverServer = NULL;

/**
 * Used for URL arguments
 */
#define kDefaultSessionParamName "$sessionid$"

#define kDynServerName "dyn_server"
#define kDynServerLength 10

static char *gSessionParamName = kDefaultSessionParamName;
static int gSessionParamNameLength = 11;

#define kCookieHeaderName "cookie"


/* 0 == no info known re: DRP servers;
   !0 == info has been obtained from DM */
/* drpContextBlock * a */
#define serverInfoAvailable(a)   (gCMLDRP_InfoVersion != 0)


/* r: drpServerRecord *
   evaluates to: 0 == server not known to be active; !0 == server active */
#define serverActive(r)                                 \
 ((r) && ((r)->flags & kDrpServerFlags_Active))

/* r: drpServerRecord *
   evaluates to: 0 == server not accepting new sessions */
#define serverAcceptingNewSessions(r)                                 \
 ((r) && ((r)->flags & kDrpServerFlags_AcceptingNewSessions))

/* r: drpServerRecord *
   evaluates to: 1 == server is known to be overloaded */
#define serverOverloaded(r)                                 \
 ((r) && ((r)->flags & kDrpServerFlags_Overloaded))

/* r: drpServerRecord *
   evaluates to: 1 == server seems to be down */
#define serverSeemsDown(r)                                 \
 ((r) && ((r)->flags & kDrpServerFlags_SeemsDown))

/* clear DRP server's active flag
   r: drpServerRecord *
   */
#define markServerInactive(r)               \
 ((r)->flags &= ~kDrpServerFlags_Active)


/* Set the DRP servers seems down flag
   r: drpServerRecord *
   */
#define markServerSeemsDown(r)               \
 ((r)->flags |= kDrpServerFlags_SeemsDown)


/* 0: parameter p requires a single response object;
   !0: parameter p requires a list (indexed) of responses (e.g. headers) */
#define paramDemandsListResponse(p) \
  ((p.name.type == kDefined) && ((p.name.value.defined == kParameters) || \
				 (p.name.value.defined == kHeaders)))

/* 0: parameter p requires a DRP 4-byte Integer response;
   !0: parameter p requires a string-type response */
#define paramDemandsIntResponse(p) \
  ((p.name.type == kDefined) && ((p.name.value.defined == kContentLength) || \
				 (p.name.value.defined == kServerPort)))

/* establish the DRP server record pointed to by recPtr as the "current 
   server" for context-block pCB */
#define setCurrentServer(pCB,recPtr)   \
 (pCB)->currentServer = (recPtr)


/* static function prototypes */
static DrpServiceStatus forceUpdateFromDM(drpContextBlock * pCB);

static DrpServiceStatus drpService_sid(drpContextBlock * pCB);
static DrpServiceStatus drpService_load(drpContextBlock * pCB);
static DrpServiceStatus drpService_route(drpContextBlock * pCB);
static int isStaticallyRoutedRequest(drpContextBlock * pCB);
static int getDrpServerInfoFromSid(drpContextBlock * pCB,
                                   unsigned long *oAddr,
                                   unsigned short *oPort);
static DrpServiceStatus tryServer(drpContextBlock * pCB,
                                  drpServerRecord * drpServer);
static DrpServiceStatus drpService_server(drpContextBlock * pCB);
#ifdef _CPOOL
static int pooledConnectToCurrentServer(drpContextBlock * pCB);
#endif
static int connectedToCurrentServer(drpContextBlock * pCB);
static int connectToCurrentServer(drpContextBlock * pCB);
static DrpServiceStatus sendBeginRequest(drpContextBlock * pCB);
static DrpServiceStatus handleDrpServerRequest(drpContextBlock * pCB);

static DrpServiceStatus handleGetParameters(drpContextBlock * pCB);
static DrpServiceStatus handleReadData(drpContextBlock * pCB);
static DrpServiceStatus handleWriteData(drpContextBlock * pCB);
static DrpServiceStatus handleFlush(drpContextBlock * pCB);
static DrpServiceStatus handleClose(drpContextBlock * pCB);
static DrpServiceStatus handleHeader(drpContextBlock * pCB);
static DrpServiceStatus handleReplyPacket(drpContextBlock * pCB);

static DrpServiceStatus doSendHeader(drpContextBlock * pCB);

static DrpServiceStatus handleGetAllHeaders(drpContextBlock * pCB);

static int getSessionID(drpContextBlock * pCB);

static DrpServiceStatus batchHeaders(drpContextBlock * pCB);
static DrpServiceStatus dispatchHeaders(drpContextBlock * pCB);

static DrpServiceStatus sendParameter(drpContextBlock * pCB,
                                      drpByte iDefinedParam,
                                      drpIdentifier * iIdentParam);

static int copyGlobalDrpServerInfo(drpContextBlock * pCB);

#if 0
/* obsoleted - see end of file */
static DrpServiceStatus handleGetAllParameters(drpContextBlock * pCB);
static void getNextQueryArg(char *pBuffer, int namePtr, int *oValPtr,
                            int *oNextPtr);
#endif

#if 0
/* useful for debugging */
void debugPrint(const char *message)
{
    FILE *debugOut = fopen("d:\\debug.log", "a");
    if (debugOut != NULL) {
        fputs(message, debugOut);
        fclose(debugOut);
    }
}
#endif

/**
 * drpInitialize
 * initialize the DRP2 subsystem for a connection module instance.  should
 *   be invoked once for each process (not thread) containing a CM instance,
 *   before any DRP2 service is performed.
 */
void drpInitialize(void)
{

#ifdef _DRP_MTHREAD
    srand((unsigned) time(NULL));

#else

    /* need a better seed for multiple processes otherwise each process
       will start with the same seed or a similar seed. */

#ifdef WIN32
    /* use the millisecond part of the time as the seed. */
    struct _SYSTEMTIME tp;
    GetSystemTime(&tp);
    srand(tp.wMilliseconds);

#else
    /* use the microsecond part of the time as the seed. */
    unsigned int seed = 0;
    struct timeval tp;
    int ret = gettimeofday(&tp, 0);
    if (!ret) {
        seed = (unsigned int) tp.tv_usec;
    } else {
        /* this should never happen. use ftime instead. */
        struct timeb tb;
        ret = ftime(&tb);
        if (!ret) {
            seed = (unsigned int) tb.millitm;
        } else {
            /* we should REALLY never get here. */
            seed = (unsigned int) time(0);
        }
    }
    srand(seed);

#endif                          /* ifdef WIN32 */

#endif                          /* ifdef _DRP_MTHREAD */
}


/**
 * drpReset
 * Reset a DRP Context Block between service requests
 * pCB: context block to reset
 * note: the context block object should be byte-cleared to 0 when created
 *       by the shell
 */
void drpReset(drpContextBlock * pCB)
{
    pCB->serviceStatus = kIdle;

    /* Was this request targeted with dyn_server=... */
    pCB->isTargetedRequest = 0;

    /* reset request logging to off */
    pCB->isLoggedRequest = 0;

    /* reset debugging to off */
    pCB->debugEnabled = 0;

    /* create or reset internal buffers */
    if (!pCB->ioBuffer.buffer)
        createGrowBuffer(&(pCB->ioBuffer), kMainBufferSize);
    else
        clearGrowBuffer_m((&pCB->ioBuffer));

    if (!pCB->ioBuffer2.buffer)
        createGrowBuffer(&(pCB->ioBuffer2), kMinorBufferSize);
    else
        clearGrowBuffer_m((&pCB->ioBuffer2));

    if (!pCB->headers.buffer)
        createGrowBuffer(&(pCB->headers), kMinorBufferSize);
    else
        clearGrowBuffer_m((&pCB->headers));

    if (!pCB->headers2.buffer)
        createGrowBuffer(&(pCB->headers2), kMinorBufferSize);
    else
        clearGrowBuffer_m((&pCB->headers2));

    if (!pCB->statusBuffer.buffer)
        createGrowBuffer(&(pCB->statusBuffer), kMinorBufferSize);
    else
        clearGrowBuffer_m((&pCB->statusBuffer));

    /* create or reset socket IO buffers */
    if (!pCB->recvBuf.data) {
        pCB->recvBuf.capacity = kSockBufSize;
        pCB->recvBuf.data = (char *) malloc(pCB->recvBuf.capacity);
    } else {
        pCB->recvBuf.curPtr = 0;
        pCB->recvBuf.used = 0;
    }

    if (!pCB->sendBuf.data) {
        pCB->sendBuf.capacity = kSockBufSize;
        pCB->sendBuf.data = (char *) malloc(pCB->sendBuf.capacity);
    } else {
        pCB->sendBuf.curPtr = 0;
        pCB->sendBuf.used = 0;
    }

    pCB->statusSetByServer = 0;
    pCB->statusSentToAgent = 0;
    pCB->statusCode = 0;
    pCB->statusMesg.type = kNull;
    pCB->sessionID[0] = '\0';
}

/**
 * drpSetFailoverServer
 * Set the hostname and port number of the server to redirect requests to
 * in the event of a total application failure.
 */
void drpSetFailoverServer(const char *newServer)
{
    if (gFailoverServer != NULL)
        free(gFailoverServer);
    if (newServer == NULL)
        gFailoverServer = NULL;
    else
        gFailoverServer = (char *) strdup(newServer);
}

/**
 * drpSetSessionCookieName
 * Set the session cookie name used by this server.
 */
void drpSetSessionCookieName(const char *pCookieName)
{
    if (pCookieName) {
        gSessionCookieName = (char *) strdup(pCookieName);
        gSessionCookieNameLength = strlen(pCookieName);
    }
}

/**
 * drpService
 * Service the current request
 * return: 0 == success; !0 == failure (details in pCB->serviceStatus)
 */
int drpService(drpContextBlock * pCB)
{
    int returnValue = kDrpServiceOk;

#ifdef _DRP_MTHREAD
    int acquiredHandler = 1;
    /* first: check to see if we are not exceeding our handler limit */
    if (acquireHandler() != 0) {
        returnValue = kDrpServiceFailover;
        acquiredHandler = 0;
        goto DRP_SVC_EXIT;
    }
#endif

#ifndef _DRP_MTHREAD
    /*
     * In non-multi-threaded servers, we need to poll the load manager
     * periodically to update the server table.
     */
    if (time(NULL) > gLastUpdateTime + kSingleThreadPollSeconds) {
        forceUpdateFromDM(pCB);
    }
#endif

    /* next: check to see if we should update local copy of drp-server
       info from the CMLDRP system */
    if ((pCB->drpServerInfoVersion != gCMLDRP_InfoVersion) ||
        (!serverInfoAvailable(pCB))) {
        int status = copyGlobalDrpServerInfo(pCB);
        if (status != 0 && pCB->debugEnabled)
            pCB->logMessage(pCB->shellData, kLog_Info,
                            kLogMessage_OneString,
                            "can't global global server info", NULL);
    }

    pCB->serviceStatus = kConnectingToServer;

    if (!serverInfoAvailable(pCB)) {
        if (pCB->debugEnabled)
            pCB->logMessage(pCB->shellData, kLog_Info,
                            kLogMessage_OneString,
                            "no serverInfo available", NULL);
        /* copyGlobalServerInfo will have attempted to force a data update
           if no server info available, so don't do so here */

        /* if no server info is available, probably the load manager is down
         * or some other catastrophic failure.  try to fail over to a different
         * server.
         */
        getSessionID(pCB);      /* extract session ID information, if any */
        returnValue = kDrpServiceFailover;

        pCB->logMessage(pCB->shellData, kLog_Error,
                        kLogMessage_NoDRPServerAvailable, NULL, NULL);
        goto DRP_SVC_EXIT;
    }

    /* if there is a session ID cookie, route to the server that it came
     * from.
     */
    if (getSessionID(pCB)) {
        returnValue = drpService_sid(pCB);
        /* errors will be logged by drpService_sid() */
        if (returnValue != kDrpServiceOk && pCB->isTargetedRequest) {
            if (pCB->debugEnabled)
                pCB->logMessage(pCB->shellData, kLog_Error,
                                kLogMessage_OneString,
                                "aborting failed to deliver request to specic dyn_server",
                                NULL);
            goto DRP_SVC_EXIT;
        }
        if (returnValue != kDrpServiceOk && pCB->debugEnabled) {
            char buf[256];
            sprintf(buf,
                    "failed sending request to session server with sessionId=%s",
                    pCB->sessionID);
            pCB->logMessage(pCB->shellData, kLog_Error,
                            kLogMessage_OneString, buf, NULL);
        }
    }

    /* there is no session ID cookie; if this is one of the URLs that
     * must be hashed to a particular server, do it.
     */
    else if (isStaticallyRoutedRequest(pCB)) {
        returnValue = drpService_route(pCB);
        /* errors will be logged by drpService_load() */
    }

    /* if the request wasn't serviced by either SID redirection or static
     * routing then service it by load.
     */
    if (pCB->serviceStatus < kServicingRequest) {
        returnValue = drpService_load(pCB);
        /* errors will be logged by drpService_load() */
    }

  DRP_SVC_EXIT:

    /* If a total application failover has occurred, try to redirect the
     * request to a different application server.
     */
    if ((gFailoverServer != NULL) && (returnValue == kDrpServiceFailover)) {
        drpIdentifier paramName;
        growBuffer requestUrl;
        growBuffer failoverUrl;
        growBuffer name;
        growBuffer value;

        /* we should verify that this is a redirectable method (ie, not POST)
         * here
         */

        createGrowBuffer(&requestUrl, kMinorBufferSize);
        createGrowBuffer(&failoverUrl, kMinorBufferSize);
        createGrowBuffer(&name, kMinorBufferSize);
        createGrowBuffer(&value, kMinorBufferSize);

        /* extract the request URL information
         */
        paramName.name.type = kDefined;
        paramName.name.value.defined = kPathInfo;
        paramName.hasIndex = 0;
        pCB->getParameter(pCB->shellData, &paramName, &requestUrl);

        concatenate(&failoverUrl, "http://");
        concatenate(&failoverUrl, gFailoverServer);
        if (requestUrl.buffer[0] != '/')
            concatenate(&failoverUrl, "/");
        concatenate(&failoverUrl, requestUrl.buffer);

        /* if there's no session ID information already in the URL, try to add
         * it.
         */
        if (!strstr(requestUrl.buffer, "$sessionid$") &&
            (strlen(pCB->sessionID) > 0)) {
            concatenate(&failoverUrl, ";$sessionid$");
            concatenate(&failoverUrl, pCB->sessionID);
        }

        /* assemble the HTTP header and send it on its way
         */
        pCB->statusCode = kFailoverStatusCode;  /* Moved Temporarily */
        pCB->statusSetByServer = 1;
        /*pCB->statusMesg = kFailoverStatusMessage; -- gotta convert this */
        setGrowBuffer(&name, "Location");
        pCB->setResponseHeader(pCB->shellData, &name, &failoverUrl);
        setGrowBuffer(&name, "Content-Type");
        setGrowBuffer(&value, "text/html");
        pCB->setResponseHeader(pCB->shellData, &name, &value);
        returnValue = doSendHeader(pCB);

        /* Emit a reasonable error page.
         */
        if (returnValue == kDrpServiceOk) {
            setGrowBuffer(&value, "<html>\n");
            concatenate(&value, "<title>Application Error</title>\n");
            concatenate(&value, "<h1>Application Error</h1>\n");
            concatenate(&value,
                        "A recoverable application error has occurred.  If you would like to continue, please use ");
            concatenate(&value, "<a href=\"");
            concatenate(&value, failoverUrl.buffer);
            concatenate(&value, "\">");
            concatenate(&value, failoverUrl.buffer);
            concatenate(&value, "</a>.\n");
            concatenate(&value, "</body>\n");
            concatenate(&value, "</html>\n");
            returnValue =
                pCB->writeToAgent(pCB->shellData, &value, value.used);
        }

        /* emit redirection page HTML here */
        if (returnValue != kDrpServiceOk)
            returnValue = handleFlush(pCB);
        handleClose(pCB);

        destroyGrowBuffer(&requestUrl);
        destroyGrowBuffer(&failoverUrl);
        destroyGrowBuffer(&name);
        destroyGrowBuffer(&value);
    }
#ifdef _DRP_MTHREAD
    if (acquiredHandler) {
        releaseHandler();
    }
#endif

    return returnValue;
}


#ifdef _DRP_MTHREAD
#define kSleepTime_WaitingForUpdate 250 /* msec */
#define kWaitTime_WaitingForUpdate 1    /* sec */
#else
#define kSleepTime_WaitingForUpdate 1   /* sec */
#define kWaitTime_WaitingForUpdate 1    /* sec */
#endif



/**
 * noteDrpServerSeemsDown
 * Update the global flags for a Drp server to note
 * that it seems to be down
 * return: 0 == success; !0 == failure
 */
static int noteDrpServerSeemsDown(drpContextBlock * pCB)
{
    /* NB: must obtain write lock on global drp server info in order
       to update a global flag for this server.  This is an expensive
       operation only to be undertaken in extreme cases where the
       drp server seems to be down
     */
    if (!getWriteLock(&gLoadDataLock, kRWL_DefaultReadTimeout)) {
        drpServerRecord *drpServer = pCB->currentServer;
        drpServerRecord **globalRecord;

        /* Find and update the location in the global server list */
        globalRecord =
            getDrpServerRecord((const drpServerRecord **) gDrpServerPtrs,
                               gNumDrpServers, drpServer->inetaddr,
                               drpServer->port);

        if (globalRecord != NULL) {
            markServerSeemsDown((*globalRecord));
            (*globalRecord)->probability = 0;
            /* Update the info version so that all the handler threads will now
             * pick up this information
             */
            gCMLDRP_InfoVersion++;
        }
#ifdef _DRP_MTHREAD
        releaseWriteLock(&gLoadDataLock);
#endif
        return 0;
    }
    return -1;
}


/**
 * copyGlobalDrpServerInfo
 * Copy drp server information from the CMLDRP system's global vars.
 * return: 0 == success; !0 == failure
 */
static int copyGlobalDrpServerInfo(drpContextBlock * pCB)
{
    int returnValue = 0;

    if (gCMLDRP_InfoVersion == 0) {
        /* no DRP server info available yet */
        forceUpdateFromDM(pCB);

        /* did the data get updated? */
        if (gCMLDRP_InfoVersion == 0) {
            if (pCB->debugEnabled) {
                pCB->logMessage(pCB->shellData, kLog_Info,
                                kLogMessage_OneString,
                                "No drp servers in the global list", NULL);
            }
            goto COPY_GLOBAL_INFO_EXIT;
        }
    }

    /* NB: must obtain read lock on global drp server info! */
    if (!getReadLock(&gLoadDataLock, kRWL_DefaultReadTimeout)) {
        int s;
        short newServers = 0;

        /*
         * Only set the probability to 0 once we've gotten the read lock.  If
         * we fail to get the read lock, at least we can go on and use the old
         * cached version of the hosts
         */
        pCB->totalProb = 0;

        /* update information for all drp servers known to the local thread */
        for (s = 0; s < pCB->numDrpServers; s++) {
            pCB->drpServers[s].probability = gDrpServers[s].probability;
            pCB->drpServers[s].flags = gDrpServers[s].flags;
            if (pCB->drpServers[s].flags & kDrpServerFlags_Active)
                pCB->totalProb += pCB->drpServers[s].probability;
        }

        /* establish new information for all drp servers not yet known to
           local thread (i.e. newly obtained by the CMLDRP thread since last
           update from local thread) */
        while (gNumDrpServers > pCB->numDrpServers) {
            int idx = pCB->numDrpServers;

            newServers++;

            pCB->drpServers[idx].inetaddr = gDrpServers[idx].inetaddr;
            pCB->drpServers[idx].port = gDrpServers[idx].port;
            pCB->drpServers[idx].probability =
                gDrpServers[idx].probability;
            pCB->drpServers[idx].flags = gDrpServers[idx].flags;
            pCB->drpServers[idx].sock = kInvalidSocket;

            pCB->drpServerPtrs[idx] = &pCB->drpServers[idx];

            if (pCB->drpServers[idx].flags & kDrpServerFlags_Active)
                pCB->totalProb += pCB->drpServers[idx].probability;

#ifdef _CPOOL
            pCB->drpServers[idx].pool = gDrpServers[idx].pool;
#endif

            pCB->numDrpServers++;
        }

        /* update local drp server information version */
        pCB->drpServerInfoVersion = gCMLDRP_InfoVersion;

#ifdef _DRP_MTHREAD
        releaseReadLock(&gLoadDataLock);
#endif

        /* sort locally stored servers by IP:port */
        if (newServers) {
            qsort(pCB->drpServerPtrs, pCB->numDrpServers,
                  sizeof(drpServerRecord *),
                  compareDRPServerRecPtrs_ipPort);
        }
    } else {
        if (pCB->debugEnabled) {
            pCB->logMessage(pCB->shellData, kLog_Info,
                            kLogMessage_OneString,
                            "Failed to copy server info - using stale server info",
                            NULL);
        }
        returnValue = -1;
    }

  COPY_GLOBAL_INFO_EXIT:

    return returnValue;
}


/**
 * forceUpdateFromDM
 * (DM == "Dynamo Manager", i.e. CMLDRP server)
 * force communication w/ a DM to update DRP server information;
 *   wait for DRP server info to be updated before returning
 * return: 0 == success; !0 == failure (update did not occur)
 */
static DrpServiceStatus forceUpdateFromDM(drpContextBlock * pCB)
{
    int returnValue = kDrpServiceError;
    time_t waketime;
    unsigned int infoVersion = gCMLDRP_InfoVersion;

    /* setting this flag forces CMLDRP system to attempt to update its
       globally-held drp server information */
    gCMLDRP_UpdateNow = 1;

    gLastUpdateTime = time(NULL);

    waketime = gLastUpdateTime + kWaitTime_WaitingForUpdate;
    do {
#ifdef _DRP_MTHREAD
        /* multithreaded case: wait for the CMLDRP thread to get its data */
        thread_sleep(kSleepTime_WaitingForUpdate);
#else
        /* multiprocess case: invoke CMLDRP communication directly */
        returnValue = cmldrpUpdate_MP(&gCMLDRPBlock);
#endif
        if (gCMLDRP_InfoVersion != infoVersion) {
            returnValue = kDrpServiceOk;
            break;              /* out of do/while loop */
        }
    } while (time(NULL) < waketime);

    return returnValue;
}


/**
 * getSessionID
 * Obtain a session ID from the current request, if one is available.
 * 
 * Note: this routine also looks for the dyn_server=IPADDR:PORT option.
 * If it exists, it sets pCB->isTargetedRequest and stores the targeted
 * IP address and IP port in the request.
 * return: 0 == no session ID found; !0 == session ID placed in pCB
 */
static int getSessionID(drpContextBlock * pCB)
{
    int returnValue = 0;        /* assume no session ID */
    drpIdentifier cookieHeaderName;
    drpIdentifier pathInfoParamName;
    drpIdentifier queryParamName;
    int result;

    pCB->isTargetedRequest = 0;

    /* first check in Dynamo URI args */
    pathInfoParamName.name.type = kDefined;
    pathInfoParamName.name.value.defined = kPathInfo;
    pathInfoParamName.hasIndex = 0;

    result = pCB->getParameter(pCB->shellData, &pathInfoParamName,
                               &pCB->ioBuffer);
    if (result == 0) {
        /* got some URL params */
        char *sidStart;
        char *searchStart = pCB->ioBuffer.buffer;

        pCB->ioBuffer.buffer[pCB->ioBuffer.used] = kChar_Null;

        /*
         * The URL parameter string is of the form:
         *
         *  <path-info>;$name1$value1;$name2$value2
         */
        do {
            sidStart = strchr(searchStart, kChar_Semicolon);
            if (sidStart) {
                sidStart++;     /* Skip the semicolon */
                if (!strncmp
                    (sidStart, gSessionParamName,
                     gSessionParamNameLength)) {
                    char *sidEnd;
                    int sidLen;
                    sidStart += gSessionParamNameLength;
                    /* find the ending semicolon, if any */
                    sidEnd = strchr(sidStart, kChar_Semicolon);
                    if (sidEnd == NULL)
                        sidEnd = &sidStart[strlen(sidStart)];
                    sidLen = sidEnd - sidStart;
                    if (sidLen < kMaxSessionIDLength) {
                        strncpy(pCB->sessionID, sidStart, sidLen);
                        pCB->sessionID[sidLen] = '\0';
                        returnValue = 1;
                        break;
                    }
                }
                searchStart = sidStart + 1;
            }
        } while (sidStart != NULL);
    }

    /* now check the dynamo query arguments for dyn_sid and dyn_server */
    queryParamName.name.type = kDefined;
    queryParamName.name.value.defined = kQueryString;
    queryParamName.hasIndex = 0;

    result = pCB->getParameter(pCB->shellData, &queryParamName,
                               &pCB->ioBuffer);
    if (result == 0) {
        /* got some Query params */
        char *sidStart;
        char *searchStart = pCB->ioBuffer.buffer;

        pCB->ioBuffer.buffer[pCB->ioBuffer.used] = kChar_Null;

        sidStart = searchStart;
        /*
         * The Query parameter string is of the form:
         *
         *  name1=value1&name2=value2
         */
        do {
            if (!strncmp
                (sidStart, gSessionCookieName, gSessionCookieNameLength)) {
                char *sidEnd;
                int sidLen;
                sidStart += gSessionCookieNameLength + 1;
                sidEnd = strchr(sidStart, kChar_Ampersand);
                if (sidEnd == NULL)
                    sidEnd = &sidStart[strlen(sidStart)];
                sidLen = sidEnd - sidStart;
                if (sidLen < kMaxSessionIDLength) {
                    strncpy(pCB->sessionID, sidStart, sidLen);
                    pCB->sessionID[sidLen] = '\0';
                    returnValue = 1;
                }
            } else
                if (!strncmp(sidStart, kDynServerName, kDynServerLength)) {
                unsigned long serverAddr = 0;
                char *sep;
                int i;

                sidStart += kDynServerLength + 1;

                pCB->isTargetedRequest = 1;

                for (i = 0; i < 4; i++) {
                    char tmpbuf[128];
                    char searchChar;
                    if (i == 3)
                        searchChar = kChar_Colon;
                    else
                        searchChar = kChar_Dot;
                    sep = strchr(sidStart, searchChar);
                    if (sep != NULL) {
                        serverAddr <<= 8;
                        strncpy(tmpbuf, sidStart, sep - sidStart);
                        tmpbuf[sep - sidStart] = '\0';
                        serverAddr |= atoi(tmpbuf);
                        sidStart = sep + 1;
                    }
                    /*
                     * Bad IP address format
                     */
                    else {
                        pCB->isTargetedRequest = 0;
                        break;
                    }
                }
                if (pCB->isTargetedRequest) {
                    pCB->targetedServerPort = atoi(sidStart);
                    if (pCB->targetedServerPort == 0) {
                        pCB->isTargetedRequest = 0;
                    } else {
                        /*
                         * Convert this from host to network byte order
                         */
                        pCB->targetedServerAddr = htonl(serverAddr);
                        returnValue = 1;
                        if (pCB->debugEnabled) {
                            char buf[128];
                            sprintf(buf,
                                    "specific dyn_server=%lu.%lu.%lu.%lu:%hu",
                                    pCB->targetedServerAddr & 0xff,
                                    (pCB->targetedServerAddr >> 8) & 0xff,
                                    (pCB->targetedServerAddr >> 16) & 0xff,
                                    (pCB->targetedServerAddr >> 24) & 0xff,
                                    pCB->targetedServerPort);
                            pCB->logMessage(pCB->shellData, kLog_Info,
                                            kLogMessage_OneString, buf,
                                            NULL);
                        }
                    }
                }
            }
            searchStart = strchr(sidStart, kChar_Ampersand);
            /*
             * Skip the ampersand 
             */
            sidStart = searchStart + 1;
        } while (searchStart != NULL);
    }

    if (returnValue == 0) {
        /* SID not found in dynamo url args; try cookie header */
        cookieHeaderName.hasIndex = 0;
        cookieHeaderName.name.type = kAscii;
        cookieHeaderName.name.numChars = strlen(kCookieHeaderName) + 1;
        cookieHeaderName.name.value.ascii = kCookieHeaderName;

        result = pCB->getParameter(pCB->shellData, &cookieHeaderName,
                                   &pCB->ioBuffer);
        if (result == 0) {
            /* there is something in the cookie header */
            char *sidStart;

            pCB->ioBuffer.buffer[pCB->ioBuffer.used] = kChar_Null;
            sidStart = strstr(pCB->ioBuffer.buffer, gSessionCookieName);
            if (sidStart) {
                sidStart += gSessionCookieNameLength;
                if (*sidStart == kChar_Equals) {
                    char *sidEnd;
                    int sidLen;
                    sidStart++; /* now sidStart points at 1st char in sid */
                    /* This is a special char that represents an invalid session id */
                    if (*sidStart != '-') {
                        sidEnd = strchr(sidStart, kChar_Semicolon);
                        if (sidEnd == NULL)
                            sidEnd = &sidStart[strlen(sidStart)];
                        sidLen = sidEnd - sidStart;
                        if (sidLen < kMaxSessionIDLength) {
                            strncpy(pCB->sessionID, sidStart, sidLen);
                            pCB->sessionID[sidLen] = '\0';
                            returnValue = 1;
                        }
                    }
                }
            }
        }
    }

    return returnValue;
}

/**
 * getShellParameter
 * Helper function used to extract a shell parameter into a growBuffer.
 * return: 0 on success, !0 on failure.
 */
int getShellParameter(drpContextBlock * pCB, int whichParam,
                      growBuffer * retBuf)
{
    drpIdentifier paramName;
    int retValue;

    paramName.name.type = kDefined;
    paramName.name.value.defined = whichParam;
    paramName.hasIndex = 0;
    retValue = pCB->getParameter(pCB->shellData, &paramName, retBuf);
    if (retValue == 0) {
        if (retBuf->buffer[0] == '\0')  /* failed if buffer is empty */
            retValue = -1;
    }
    return retValue;
}

/**
 * isStaticallyRoutedRequest
 * See if the URI of the incoming request exists in the statically routed
 *   page table.
 * returns 1 (true) if so, 0 (false) if not.
 */
int isStaticallyRoutedRequest(drpContextBlock * pCB)
{
    growBuffer buf;
    int returnValue;

    if (createGrowBuffer(&buf, 1024) == NULL)
        return 0;               /* fails due to lack of memory */
    if (getShellParameter(pCB, kRequestURI, &buf) == 0)
        returnValue = isStaticallyRoutedPage(buf.buffer);
    else
        returnValue = 0;        /* no URI found */
    destroyGrowBuffer(&buf);
    return returnValue;
}

/**
 * drpService_sid
 * Use the session-ID from the current request to determine the DRP server
 *   with which to communicate; contact that server and use it to service
 *   the request
 * return: 0 == success; !0 == failure
 */
static DrpServiceStatus drpService_sid(drpContextBlock * pCB)
{
    int returnValue = kDrpServiceOk;
    unsigned long serverAddr;
    unsigned short serverPort;
    int result;

    /* get IP:port of requested DRP server from session id (SID stored
       in context block) */
    if (pCB->isTargetedRequest) {
        result = 0;
        serverAddr = pCB->targetedServerAddr;
        serverPort = pCB->targetedServerPort;
    } else {
        result = getDrpServerInfoFromSid(pCB, &serverAddr, &serverPort);
    }
    if (result == 0) {
        drpServerRecord **serverRecord;
        serverRecord =
            getDrpServerRecord((const drpServerRecord **) pCB->
                               drpServerPtrs, pCB->numDrpServers,
                               serverAddr, serverPort);
        if (!serverRecord) {
            if (pCB->debugEnabled) {
                char buf[1024];
                sprintf(buf,
                        "Unable to find dynamo server with addr=%lu.%lu.%lu.%lu port=%hu",
                        serverAddr & 0xff, (serverAddr >> 8) & 0xff,
                        (serverAddr >> 16) & 0xff,
                        (serverAddr >> 24) & 0xff, serverPort);
                pCB->logMessage(pCB->shellData, kLog_Error,
                                kLogMessage_OneString, buf, NULL);
            }
            /* most likely case: this session was started by another context
               with more current info about avail drp servers, which this
               context does not yet have; obtain that info */
            returnValue = forceUpdateFromDM(pCB);
            serverRecord =
                getDrpServerRecord((const drpServerRecord **) pCB->
                                   drpServerPtrs, pCB->numDrpServers,
                                   serverAddr, serverPort);
        }

        /* now, if server still not active, we can't use it */
        if ((!serverRecord) || (!serverActive((*serverRecord))))
            returnValue = kDrpServiceNoServer;
        else
            returnValue = tryServer(pCB, *serverRecord);
    }

    return returnValue;
}

/**
 * drpService_load
 * Use load-balancing to determine the DRP server with which to communicate
 *   to service the current request; contact that server and use it to
 *   complete service
 * return: 0 == success; !0 == failure
 */
static DrpServiceStatus drpService_load(drpContextBlock * pCB)
{
    int returnValue = -1;
    unsigned int randNum;
    int s;
    drpServerRecord *drpServer;

    if (pCB->debugEnabled && pCB->totalProb <= 0) {
        char buf[256];
        sprintf(buf, "total probability is: %d numServers=%d",
                pCB->totalProb, pCB->numDrpServers);
        pCB->logMessage(pCB->shellData, kLog_Info, kLogMessage_OneString,
                        buf, NULL);
        for (s = 0; s < pCB->numDrpServers; s++) {

            sprintf(buf,
                    "probability for server: %lu.%lu.%lu.%lu "
                    "port=%hu flags=%hu is: %hu",
                    pCB->drpServers[s].inetaddr & 0xff,
                    (pCB->drpServers[s].inetaddr >> 8) & 0xff,
                    (pCB->drpServers[s].inetaddr >> 16) & 0xff,
                    (pCB->drpServers[s].inetaddr >> 24) & 0xff,
                    pCB->drpServers[s].port,
                    pCB->drpServers[s].flags,
                    pCB->drpServers[s].probability);
            pCB->logMessage(pCB->shellData, kLog_Info,
                            kLogMessage_OneString, buf, NULL);
        }
    }

    while (pCB->totalProb > 0) {
        /* get a random number from 1 to totalProb (sum of all active DRP server 
           weights); walk through the list of DRP servers to find the 
           drp server record corresponding to the random number by weight */

        drpServer = NULL;
        randNum = (rand() % pCB->totalProb) + 1;        /* 1..totalProb */

        for (s = 0; (s < pCB->numDrpServers) && (randNum > 0); s++) {
            if (serverActive(&pCB->drpServers[s])) {
                if (serverAcceptingNewSessions(&pCB->drpServers[s]) &&
                    !serverOverloaded(&pCB->drpServers[s]) &&
                    !serverSeemsDown(&pCB->drpServers[s]) &&
                    randNum <= pCB->drpServers[s].probability) {
                    drpServer = &pCB->drpServers[s];
                    break;      /* out of "for (s = 0..." loop */
                }
                /*
                 * Only subtract the probablity if this server is in the list
                 */
                randNum -= MIN(pCB->drpServers[s].probability, randNum);
            }
        }

        if (drpServer) {
            returnValue = tryServer(pCB, drpServer);
            if (returnValue == 0)
                break;          /* out of "while pCb->totalProb > 0" */
            else if (gRetryServerAfterFailure)
                break;
        } else {
            pCB->logMessage(pCB->shellData, kLog_Error,
                            kLogMessage_NoDRPServerAvailable, NULL, NULL);

            /* all servers failed; tell the HTTP server to try to fail over
             * to a different dynamo cluster.
             */
            returnValue = kDrpServiceFailover;
            break;
        }
    }

    /*
     * This is a last ditch effort to find a server to try.  If the load
     * manager mistakenly polled a dynamo and saw that it was down, it
     * would get removed from the list.  If we have no dynamos in the list,
     * then and this "retry mode" is turned on, we go through the list
     * and retry each server until we find one that works.
     */
    if (returnValue != kDrpServiceOk && pCB->totalProb == 0) {
        if (pCB->debugEnabled)
            pCB->logMessage(pCB->shellData, kLog_Info,
                            kLogMessage_OneString,
                            "total prob is 0 retrying server list", NULL);
        for (s = 0; s < pCB->numDrpServers; s++) {
            /*
             * Unless the server has been explicitly marked as inactive, overloaded
             * or down.
             */
            if (serverActive(&pCB->drpServers[s]) &&
                serverAcceptingNewSessions(&pCB->drpServers[s]) &&
                !serverOverloaded(&pCB->drpServers[s]) &&
                !serverSeemsDown(&pCB->drpServers[s])) {
                drpServer = &pCB->drpServers[s];

                returnValue = tryServer(pCB, drpServer);
                if (returnValue == kDrpServiceOk) {
                    if (pCB->debugEnabled)
                        pCB->logMessage(pCB->shellData, kLog_Info,
                                        kLogMessage_OneString,
                                        "successfully retried server",
                                        NULL);
                    break;
                }
            }
        }

        /* we didn't find any dynamos that work; tell the HTTP server
         * to fail over, if possible.
         */
        if (returnValue != kDrpServiceOk)
            returnValue = kDrpServiceFailover;
    }


    return returnValue;
}

/**
 * drpService_route
 * Use a deterministic algorithm to route to determine the DRP server with
 *   which to communicate to service the current request; contact that
 *   server and use it to complete service.
 *
 *   The selection algorithm uses a string hash value plus the two high-order
 *   octets in the client IP address.  The latter adjustment is done as
 *   a way to better distribute the load amongst the servers.  Only the
 *   high-order octets are used because AOL (and possibly other) clients can
 *   shift IP addresses between requests, but it is extremely unlikely that
 *   they will shift between B-class networks.
 *
 *   In case of server failure the next working server in the server table
 *   is used instead.  This fallback has a window where two requests that
 *   should go to the same server will go to different servers if the
 *   selected server should fail or return to service between requests.
 *   For Ad Station's use this is felt to be acceptable.
 *
 * return: 0 == success; !0 == failure
 */
static DrpServiceStatus drpService_route(drpContextBlock * pCB)
{
    int hashValue;
    int firstServerNum;
    int serverNum;
    growBuffer buf;
    int returnValue = -1;
    int octetValue = 0;
    int ipValue = 0;
    int dotCount = 0;

    /* no servers, no routing.
     */
    if (pCB->numDrpServers == 0) {
        pCB->logMessage(pCB->shellData, kLog_Error,
                        kLogMessage_NoDRPServerAvailable, NULL, NULL);
        return kDrpServiceNoServer;
    }

    if (createGrowBuffer(&buf, 1024) == NULL)
        return kDrpServiceNoMemory;     /* memory allocation failure */

    /* calculate a hash value for the User-Agent field, if known
     */
    if (getShellParameter(pCB, kUserAgent, &buf) != 0)
        hashValue = 0;
    else
        hashValue = getStringHashValue(buf.buffer);

    /* adjust the hash value using the 2 high-order bytes of the IP address
     * for better load balancing.
     */
    if (getShellParameter(pCB, kRemoteAddr, &buf) == 0) {
        char *p = &buf.buffer[0];

        /* parse the first two octets of the dotted IP address into a value.
         */
        while (*p != '\0') {
            if ((*p >= '0') && (*p <= '9'))
                octetValue = (octetValue * 10) + (*p - '0');
            else if (*p == '.') {
                ipValue = (ipValue << 8) + octetValue;
                if (++dotCount == 2)    /* only first two octets */
                    break;
                octetValue = 0;
            }
            p++;
        }

        hashValue += ipValue;
    }

    /* use the hash to determine the first server to try.
     */
    serverNum = firstServerNum = hashValue % pCB->numDrpServers;

    for (;;) {

        /* if the server is thought to be responding, try it.
         */
        if (pCB->drpServers[serverNum].probability != 0) {
            returnValue = tryServer(pCB, &pCB->drpServers[serverNum]);
            if (returnValue == 0)       /* request went through */
                goto cleanup;
        }

        /* try the next server in the table
         */
        serverNum = (serverNum + 1) % pCB->numDrpServers;

        /* if we've tried all servers in the table, we're SOL.
         */
        if (serverNum == firstServerNum) {
            pCB->logMessage(pCB->shellData, kLog_Error,
                            kLogMessage_NoDRPServerAvailable, NULL, NULL);
            goto cleanup;
        }
    }

  cleanup:
    destroyGrowBuffer(&buf);
    return returnValue;
}

/**
 * tryServer
 * Attempts to service the request with the given server, adjusting
 *   the server probabilities if the request fails.
 * return: 0 == success; !0 == failure
 */
static DrpServiceStatus tryServer(drpContextBlock * pCB,
                                  drpServerRecord * drpServer)
{
    int returnValue;

    setCurrentServer(pCB, drpServer);

    /* drpService_server actually attempts the service */
    returnValue = drpService_server(pCB);

    /* if the attempt failed, log it and return.
     */
    if (returnValue != 0) {
        char portString[8];
        struct in_addr *inAddr;

        sprintf(portString, "%d", drpServer->port);
        inAddr = (struct in_addr *) &drpServer->inetaddr;
        pCB->logMessage(pCB->shellData, kLog_Warn,
                        kLogMessage_DRPServerUnavailable,
                        inet_ntoa(*inAddr), portString);
        /* 
         * mark server as unavailable, restart loop and
         * factor this server's unavailability into random number range 
         */
        if (!gRetryServerAfterFailure) {
            /*
             * Don't do this twice - some of the error conditions already do this
             * apparently.
             */
            if (serverActive(drpServer)) {
                markServerInactive(drpServer);
                pCB->totalProb -= drpServer->probability;
            }
        }
        returnValue = kDrpServiceError;
    }

    return returnValue;
}

/**
 * drpService_server
 * Use the current server to service the current request
 * return: 0 == success; !0 == failure (details in pCB->serviceStatus)
 */
static DrpServiceStatus drpService_server(drpContextBlock * pCB)
{
    int returnValue = kDrpServiceOk;

#ifdef _CPOOL
    connectionPool *pool;
#endif

    /* If we think this server is down we will return a failure */
    if (serverSeemsDown(pCB->currentServer)) {
        return kDrpServiceFailover;
    }

    if (!connectedToCurrentServer(pCB)) {

#ifdef _CPOOL
        /* Hold on to the pool pointer in case we fail to connect
           to this server compeletely in which case 
           pCB->currentServer will be set to NULL.
         */
        pool = pCB->currentServer->pool;
        returnValue = pooledConnectToCurrentServer(pCB);
#else
        returnValue = connectToCurrentServer(pCB);
#endif

        if (returnValue) {
            /* NB: don't log error; connectToCurrentServer will have done so */
            goto DRP_SVC_SERVER_EXIT;
        }
    }

    returnValue = sendBeginRequest(pCB);

    if (!pCB->isLoggedRequest) {
        while ((pCB->serviceStatus < kServiceComplete)
               && (returnValue == 0)) {
            returnValue = handleDrpServerRequest(pCB);
        }
    }
    /*
     * We don't get any replies here
     */
    else
        pCB->serviceStatus = kServiceComplete;

  DRP_SVC_SERVER_EXIT:

#ifdef _CPOOL

    /* Unless the Connection Pool is disabled, or the checkout
       operation timed out, a connection has to be checked back
       into the connection pool.
     */
    if (gCP_Enabled && (returnValue != kCP_OperationTimedOut)) {
        int result;

        if (pCB->currentServer)
            result = checkinCP(pCB->currentServer->pool,
                               pCB->currentServer->sock);
        /* Case where the current server has been set to
           NULL
         */
        else
            result = checkinCP(pool, kInvalidSocket);

        switch (result) {
        case kCP_CheckedIn:{
                if (pCB->currentServer)
                    pCB->currentServer->sock = kInvalidSocket;
/*
      if (pCB->debugEnabled) {
	char buf[100];	  
	sprintf(buf, "Checked In: pool %x : Available in pool = %d", 
	        pool,
		getAvailableCP(pool));
	pCB->logMessage(pCB->shellData,
			kLog_Info,
			kLogMessage_OneString, 
			buf,
			NULL);
        } 
*/
                break;
            }
        case kCP_OperationTimedOut:{
/*      
      if (pCB->debugEnabled) {
	char buf[100];	  
	sprintf(buf, "Check In timed out: pool %x", pool);		
	pCB->logMessage(pCB->shellData,
			kLog_Info,
			kLogMessage_OneString, 
			buf,
			NULL);
      }
*/
                break;
            }
        case kCP_Full:{
/*
      if (pCB->debugEnabled) {
	char buf[100];	  
	sprintf(buf, "Connection pool full: %x", pool);		
	pCB->logMessage(pCB->shellData, kLog_Info,
			kLogMessage_OneString, 
			buf,
			NULL);
      }
*/
                break;
            }
        }
    }
#endif

    return returnValue;
}


/**
 * connectedToCurrentServer
 * Determine whether this execution context has an active socket 
 *   connection to the context block's current DRP server
 * return: 0 == NOT connected; !0 == connected
 */
static int connectedToCurrentServer(drpContextBlock * pCB)
{
    int returnValue = 0;        /* assume not */

    if (pCB->currentServer && (pCB->currentServer->sock != kInvalidSocket)) {
        fd_set checkSet;
        int nfds;
        struct timeval timeout;

        nfds = ((int) pCB->currentServer->sock) + 1;

        timeout.tv_sec = 0;
        timeout.tv_usec = 0;

        FD_ZERO(&checkSet);
        FD_SET(pCB->currentServer->sock, &checkSet);
        if (select(nfds, &checkSet, NULL, NULL, &timeout)) {
            /* socket has data ready to read; this is bad -- either we're out
               of synch with server on the DRP protocol, or the conn has been
               dropped by the server -- in either case, we should close it */
            closesocket(pCB->currentServer->sock);
            pCB->currentServer->sock = kInvalidSocket;
/*
      if (pCB->debugEnabled) {
        char buf[256];
        sprintf(buf,"Closed bad connection to: %d.%d.%d.%d sock=%d", 
                pCB->currentServer->inetaddr & 0xff, 
                (pCB->currentServer->inetaddr >> 8) & 0xff,
                (pCB->currentServer->inetaddr >> 16) & 0xff,
                (pCB->currentServer->inetaddr >> 24) & 0xff,
                 pCB->currentServer->sock);
        pCB->logMessage(pCB->shellData, kLog_Info,
                        kLogMessage_OneString, buf, NULL);
      }
*/
        } else {
            returnValue = 1;
        }
    }

    return returnValue;
}


#ifdef _CPOOL
/**
 * pooledConnectToCurrentServer
 * Establish a socket connection to the context block's current DRP server,
 * or retrieve an existing socket connection from the connection pool.
 * Note that this can block if the number of sockets managed by the
 * connection pool exceeds 
 * return: 0 == success; !0 == failure
 */
static int pooledConnectToCurrentServer(drpContextBlock * pCB)
{
    int checkOutResult;

    /* If the connection pool is enabled first try to check
       out a socket from the pool. 
     */
    if (gCP_Enabled) {

        gDebugInfo.numWaitingOnPool++;
        checkOutResult = checkoutCP(pCB->currentServer->pool,
                                    &(pCB->currentServer->sock));
        gDebugInfo.numWaitingOnPool--;

        switch (checkOutResult) {
        case kCP_CheckedOut:{
                if (connectedToCurrentServer(pCB)) {
/*
	if (pCB->debugEnabled) {
	  char buf[100];	  
	  sprintf(buf, "Checked Out: pool %x : Available in pool = %d", 
		  pCB->currentServer->pool,
		  getAvailableCP(pCB->currentServer->pool));
	  pCB->logMessage(pCB->shellData,
			  kLog_Info,
			  kLogMessage_OneString, 
			  buf,
			  NULL);
	}
*/
                    return 0;
                } else {
                    /* We either got an invalid socket, or one that no
                       longer applies to this server. 
                     */
                    pCB->currentServer->sock = kInvalidSocket;
                    break;
                }
            }
        case kCP_OperationTimedOut:{
                /* An attempt to get a socket from the connection pool was 
                   timed out so we want to note that this is a sign that the
                   server seems to be down.
                 */

                if (pCB->debugEnabled) {
                    char buf[100];
                    sprintf(buf, "Check Out timed out: pool %x",
                            pCB->currentServer->pool);
                    pCB->logMessage(pCB->shellData, kLog_Info,
                                    kLogMessage_OneString, buf, NULL);
                }
                noteDrpServerSeemsDown(pCB);
                return kCP_OperationTimedOut;
            }
        }
    }
    /*  
       Where we actually attempt to connect.
     */
    return connectToCurrentServer(pCB);
}
#endif

/**
 * connectToCurrentServer
 * Establish a socket connection to the context block's current DRP server
 * return: 0 == success; !0 == failure
 */
static int connectToCurrentServer(drpContextBlock * pCB)
{
    int returnValue = 0;
    int onFlag = 1;
    struct sockaddr_in addr;
    time_t timeBefore = 0;

#ifdef DEBUG
    {
        char buf[64];
        sprintf(buf, "max connect timeout is %d", pCB->maxConnectTimeout);
        pCB->logMessage(pCB->shellData, kLog_Info, kLogMessage_OneString,
                        buf, NULL);
    }
#endif

    timeBefore = time(NULL);

    gDebugInfo.numWaitingToConnect++;

  trysocketagain:
    pCB->currentServer->sock = socket(AF_INET, SOCK_STREAM, 0);
    if (pCB->currentServer->sock == kInvalidSocket) {
        char errorNumber[12];

        /* couldn't create a socket */
        sprintf(errorNumber, "%d", net_error());
        pCB->logMessage(pCB->shellData, kLog_Error,
                        kLogMessage_CannotCreateSocket, errorNumber, NULL);
        returnValue = -1;
        goto CONNECT_CURRENT_EXIT;
    } else {
        /* turn on keep-alive; ignore result */
        setsockopt(pCB->currentServer->sock, SOL_SOCKET, SO_KEEPALIVE,
                   (char *) &onFlag, sizeof(onFlag));
        /* turn off the nagle-algorithm, so that the system will send
           teeny-weeny packets without farting around */
        setsockopt(pCB->currentServer->sock, IPPROTO_TCP, TCP_NODELAY,
                   (char *) &onFlag, sizeof(onFlag));

        addr.sin_family = AF_INET;
        addr.sin_port = htons(pCB->currentServer->port);
        addr.sin_addr.s_addr = pCB->currentServer->inetaddr;

      tryagain:
        returnValue = connect(pCB->currentServer->sock,
                              (struct sockaddr *) &addr,
                              sizeof(struct sockaddr));
        if (returnValue) {
            char errorNumber[12];

            int errorNum = net_error();
            if (errorNum == EINTR)
                goto tryagain;

            if (errorNum == ETIMEDOUT) {
                closesocket(pCB->currentServer->sock);
                pCB->currentServer->sock = kInvalidSocket;

                if (pCB->debugEnabled) {
                    char buf[256];
                    sprintf(buf,
                            "got ETIMEDOUT on connect to server: %lu.%lu.%lu.%lu sock=%d",
                            pCB->currentServer->inetaddr & 0xff,
                            (pCB->currentServer->inetaddr >> 8) & 0xff,
                            (pCB->currentServer->inetaddr >> 16) & 0xff,
                            (pCB->currentServer->inetaddr >> 24) & 0xff,
                            pCB->currentServer->sock);
                    pCB->logMessage(pCB->shellData, kLog_Info,
                                    kLogMessage_OneString, buf, NULL);
                }
                if ((timeBefore > 0) &&
                    (time(NULL) > (timeBefore + pCB->maxConnectTimeout))) {
                    noteDrpServerSeemsDown(pCB);
                    return kDrpServiceFailover;
                }
                goto trysocketagain;
            }

            /* couldn't connect to current DRP server */
            sprintf(errorNumber, "%d", net_error());

            if (!gRetryServerAfterFailure) {
                /* Be sure not to do this twice for the same server! */
                if (serverActive(pCB->currentServer)) {
                    markServerInactive(pCB->currentServer);
                    pCB->totalProb -= pCB->currentServer->probability;
                }
            }

            /*
             * Need to nuke this socket or else we'll wrongly think we are still
             * connected!
             */
            closesocket(pCB->currentServer->sock);
            pCB->currentServer->sock = kInvalidSocket;

            pCB->logMessage(pCB->shellData, kLog_Error,
                            kLogMessage_CannotConnect, errorNumber, NULL);
            pCB->currentServer = NULL;
            goto CONNECT_CURRENT_EXIT;
        }
/*
    else if (pCB->debugEnabled) {
      char buf[256];
      sprintf(buf,"Created new connection to: %d.%d.%d.%d sock=%d", 
              pCB->currentServer->inetaddr & 0xff, 
              (pCB->currentServer->inetaddr >> 8) & 0xff,
              (pCB->currentServer->inetaddr >> 16) & 0xff,
              (pCB->currentServer->inetaddr >> 24) & 0xff,
     	       pCB->currentServer->sock);
      pCB->logMessage(pCB->shellData, kLog_Info,
                      kLogMessage_OneString, buf, NULL);
    }
*/
    }

  CONNECT_CURRENT_EXIT:

    gDebugInfo.numWaitingToConnect--;

    return returnValue;
}


/**
 * sendBeginRequest
 * Send BeginRequest packet to DRP server to start DRP service cycle
 * return: 0 == success; !0 == failure
 */
static DrpServiceStatus sendBeginRequest(drpContextBlock * pCB)
{
    int returnValue = kDrpServiceOk;
    drpByte beginType;
    drpByte _0 = 0;

    gDebugInfo.numSendingRequest++;

    /*
     * Use a different header byte if we are logging, not servicing
     * this request
     */
    if (pCB->isLoggedRequest)
        beginType = kBeginLogRequest;
    else
        beginType = kBeginRequest;

    returnValue = sendByte(pCB, pCB->currentServer->sock, &beginType);

    /* also send: Method, PathInfo, ContentLength, PathTranslated, 
       Querystring, RequestURI, ServletPath, and all Headers */

    /* method */
    if (returnValue == kDrpServiceOk)
        returnValue = sendParameter(pCB, kMethod, NULL);

    /* path-info */
    if (returnValue == kDrpServiceOk)
        returnValue = sendParameter(pCB, kPathInfo, NULL);

    /* content-length */
    if (returnValue == kDrpServiceOk)
        returnValue = sendParameter(pCB, kContentLength, NULL);

    /* content-type */
    if (returnValue == kDrpServiceOk)
        returnValue = sendParameter(pCB, kContentType, NULL);

    /* path-translated */
    if (returnValue == kDrpServiceOk)
        returnValue = sendParameter(pCB, kPathTranslated, NULL);

    /* query-string */
    if (returnValue == kDrpServiceOk)
        returnValue = sendParameter(pCB, kQueryString, NULL);

    /* request-uri */
    if (returnValue == kDrpServiceOk)
        returnValue = sendParameter(pCB, kRequestURI, NULL);

    /* servlet-path */
    if (returnValue == kDrpServiceOk)
        returnValue = sendParameter(pCB, kServletPath, NULL);

    /*
     * In 3.0, we only send these values for logged requests.  Turns out
     * to be pretty common to want them and fairly slow to make the drp
     * server come back to get them.  Just send them all then..
     */
    if (returnValue == kDrpServiceOk)
        returnValue = sendParameter(pCB, kScheme, NULL);
    if (returnValue == kDrpServiceOk)
        returnValue = sendParameter(pCB, kRemoteAddr, NULL);
    if (returnValue == kDrpServiceOk)
        returnValue = sendParameter(pCB, kServerName, NULL);
    if (returnValue == kDrpServiceOk)
        returnValue = sendParameter(pCB, kServerPort, NULL);

    /*
     * In D4.0, I'm sending this as well.  Now all of the headers should
     * be there to avoid unnecessary GET_PARAMETERS calls
     */
    if (returnValue == kDrpServiceOk)
        returnValue = sendParameter(pCB, kProtocol, NULL);

    /* all headers */
    if (returnValue == kDrpServiceOk)
        returnValue = handleGetAllHeaders(pCB);

    /* terminate NV-pair list */
    if (returnValue == kDrpServiceOk)
        returnValue = sendByte(pCB, pCB->currentServer->sock, &_0);

    /* also send: block of data from agent (browser), if available */
    if (returnValue == kDrpServiceOk && !pCB->isLoggedRequest) {
        int result;

        result =
            pCB->readFromAgent(pCB->shellData, &pCB->ioBuffer,
                               pCB->ioBuffer.avail);
        if (result == 0) {
            returnValue = sendLong(pCB, pCB->currentServer->sock,
                                   (drpInt *) & pCB->ioBuffer.used);
            if (returnValue == kDrpServiceOk)
                returnValue = sendBuffer(pCB, pCB->currentServer->sock,
                                         &pCB->ioBuffer);
        } else {
            drpInt _0L = (drpInt) 0;
            returnValue = sendLong(pCB, pCB->currentServer->sock, &_0L);
        }
    }

    if (returnValue == 0) {
        returnValue = send_flush(pCB, pCB->currentServer->sock, 0);
    }

    gDebugInfo.numSendingRequest--;

    return returnValue;
}


/**
 * sendParameter
 * find the value for a requested parameter and send the response to 
 *   DRP server.  (name-value pair is sent to DRP server, so this routine
 *   can be invoked several times to construct a DRP name-value pair list
 *   which must be terminated by the caller.)
 * iParameter: if (0 < iParameter < kDefinedPlaceholder), the parameter
 *             sought is defined string iParameter; otherwise, iIdentParam
 * iIdentParam: used as lookup paramete if iParameter does not correspond
 *              to a known defined string value
 * return: 0 == success; !0 == failure
 */
static DrpServiceStatus
sendParameter(drpContextBlock * pCB, drpByte iDefinedParam,
              drpIdentifier * iIdentParam)
{
    int returnValue = 0;
    drpIdentifier paramName;
    drpIdentifier *paramPtr = NULL;
    drpByte _1 = 1;

    if ((iDefinedParam > 0) && (iDefinedParam < kDefinedPlaceholder)) {
        /* we're looking up a defined-string parameter, iDefinedParam */

        paramName.hasIndex = 0;
        paramName.name.type = kDefined;
        paramName.name.value.defined = iDefinedParam;
        paramPtr = &paramName;
    } else if (iIdentParam) {
        /* we're looking up a string parameter, iIdentParam */
        paramPtr = iIdentParam;
    } else {
        /* no good lookup parameter passed to us */
        returnValue = -1;
    }

    returnValue =
        pCB->getParameter(pCB->shellData, paramPtr, &pCB->ioBuffer2);
    if (returnValue == 0) {
        /* send "1" (indicating parameter pair follows) */
        returnValue = sendByte(pCB, pCB->currentServer->sock, &_1);
        /* send the defined-string parameter name */
        if (returnValue == 0)
            returnValue =
                sendIdentifier(pCB, pCB->currentServer->sock, paramPtr);
        /* send the parameter value */
        if (returnValue == 0) {
            if (paramDemandsIntResponse((*paramPtr))) {
                pCB->ioBuffer2.buffer[pCB->ioBuffer2.used] = kChar_Null;
                returnValue =
                    sendIntTLiteral(pCB, pCB->currentServer->sock,
                                    atol(pCB->ioBuffer2.buffer));
            } else {
                returnValue =
                    sendAsciiTLiteral(pCB, pCB->currentServer->sock,
                                      &pCB->ioBuffer2);
            }
        }
    }

    return returnValue;
}


/**
 * handleDrpServerRequest
 * Receive and handle (i.e. respond appropriately to) one DRP packet
 *   from the current DRP server
 * return: 0 == success; !0 == failure
 *   also: pCB->serviceStatus may change to reflect checkpoints in 
 *         DRP cycle
 */
static DrpServiceStatus handleDrpServerRequest(drpContextBlock * pCB)
{
    int returnValue = kDrpServiceOk;
    drpByte packetType;

    gDebugInfo.numWaitingForResponse++;
    returnValue = recvByte(pCB, pCB->currentServer->sock, &packetType);

    if (returnValue == 0) {
        switch (packetType) {
        case kReply:
            returnValue = handleReplyPacket(pCB);
            break;
        case kGetParameters:
            returnValue = handleGetParameters(pCB);
            break;
        case kReadDataStream:
            returnValue = handleReadData(pCB);
            break;

        default:
            /* unknown/unexpected packet type */
            returnValue = kDrpServiceProtocolError;
            {
                char packetTypeNumber[128];
                sprintf(packetTypeNumber, "value=%d status=%d", packetType,
                        pCB->serviceStatus);
                pCB->logMessage(pCB->shellData, kLog_Error,
                                kLogMessage_InternalProtoError,
                                packetTypeNumber, NULL);
            }
            break;
        }
    }
    gDebugInfo.numWaitingForResponse--;
    return returnValue;
}


/**
 * handleReplyPacket
 * Receive and handle reply packet information (may include HTTP status
 *   information, reply headers, response data, etc); reply with success
 *   status to DRP server if requested by server.
 * return: 0 == success; !0 == failure
 */
static DrpServiceStatus handleReplyPacket(drpContextBlock * pCB)
{
    /* This is error code for reads from dynamo */
    int returnValue = kDrpServiceOk;
    drpByte sendHeader = 0;
    drpByte flushData = 0;
    drpByte closeData = 0;
    drpByte endRequest = 0;
    drpByte replyExpected = 0;
    /* This has the return value of writes to the client */
    int result = 0;
    drpByte _0 = 0;
    drpByte _1 = 1;

    /* receive reply flags - should we flush, close, etc */
    returnValue = recvByte(pCB, pCB->currentServer->sock, &sendHeader);
    if (returnValue == kDrpServiceOk)
        returnValue = recvByte(pCB, pCB->currentServer->sock, &flushData);
    if (returnValue == kDrpServiceOk)
        returnValue = recvByte(pCB, pCB->currentServer->sock, &closeData);
    if (returnValue == kDrpServiceOk)
        returnValue = recvByte(pCB, pCB->currentServer->sock, &endRequest);
    if (returnValue == kDrpServiceOk)
        returnValue =
            recvByte(pCB, pCB->currentServer->sock, &replyExpected);

    if ((sendHeader) && (returnValue == kDrpServiceOk)) {
        /* also receive and handle header info */
        result = handleHeader(pCB);
    }

    if (returnValue == kDrpServiceOk) {
        /* also receive and send output data block */
        if (result == 0) {
            returnValue = handleWriteData(pCB);
            if (returnValue != kDrpServiceOk) {
                result = -1;
                /*
                 * Don't let this error cause the server to think that the request
                 * failed.
                 */
                returnValue = kDrpServiceOk;
                /*
                 * We had to close the socket so the protocol is done.
                 */
                if (pCB->currentServer->sock == kInvalidSocket) {
                    pCB->serviceStatus = kServiceComplete;
                    return returnValue;
                }
            }
        }
    }

    /* handle miscellaneous reply flags */
    if (result == 0 && flushData)
        result = handleFlush(pCB);
    if (result == 0 && closeData)
        result |= handleClose(pCB);
    if (replyExpected) {
        /* send 1 (success) or 0 (failure) in reply to DRP server */
        returnValue = sendByte(pCB, pCB->currentServer->sock,
                               ((result == 0) ? &_1 : &_0));
        if (returnValue == 0)
            /* force the send */
            returnValue = send_flush(pCB, pCB->currentServer->sock, 0);
    }
    if (endRequest) {

        pCB->serviceStatus = kServiceComplete;

        if (endRequest == 2) {
            closesocket(pCB->currentServer->sock);
            pCB->currentServer->sock = kInvalidSocket;
        }
#ifdef _CPOOL
        if (gCP_Enabled) {

            if (endRequest == 2) {
                /* 
                   This is an incompatable configuration - we are pooling 
                   connections on the CM side, but a DRPServer is closing 
                   connections. 
                   Therefore output an error message and disable pooling. 
                 */
                if (pCB->debugEnabled)
                    pCB->logMessage(pCB->shellData, kLog_Info,
                                    kLogMessage_OneString,
                                    "DRPServer dropped connection when connection pool was enabled",
                                    NULL);
                gCP_Enabled = 0;
            }
        }
#endif                          /* _CPOOL */
    }
    return returnValue;
}


/**
 * handleGetParameters
 * Satisfy a Get-Parameters DRP server request in progress.  This routine
 *   (with subroutines) receives the DRP identifier-list from the drp server
 *   and responds with a full DRP name-value pair list (with terminator).
 * return: 0 == success; !0 == failure
 */
static DrpServiceStatus handleGetParameters(drpContextBlock * pCB)
{
    int returnValue = kDrpServiceOk;
    drpByte identDelim;
    drpByte _0 = 0;

    if (pCB->serviceStatus < kExaminingRequest)
        pCB->serviceStatus = kExaminingRequest;
    /* as long as identDelim byte is 1, we've got more identifiers to
       read from the list sent by the DRP server */
    returnValue = recvByte(pCB, pCB->currentServer->sock, &identDelim);
    while ((identDelim == 1) && (returnValue == 0)) {
        drpIdentifier paramName;

        returnValue =
            recvIdentifier(pCB, pCB->currentServer->sock, &paramName,
                           &pCB->ioBuffer);
        if (returnValue == kDrpServiceOk) {
            if (paramName.name.type == kDefined) {
                /* special handling of defined-string requested parameters:
                   these parameters are divided into four groups; whenever a request
                   is made for a single element from any group, the values for
                   the entire group should be sent in response (the first group
                   is to be sent with the begin-request packet) */

                switch (paramName.name.value.defined) {
                case kMethod:
                case kPathInfo:
                case kContentLength:
                case kPathTranslated:
                case kQueryString:
                case kRequestURI:
                case kServletPath:
                case kHeaders:
                    /* group 1 */
                    returnValue = sendParameter(pCB, kMethod, NULL);
                    if (returnValue == kDrpServiceOk)
                        returnValue = sendParameter(pCB, kPathInfo, NULL);
                    if (returnValue == kDrpServiceOk)
                        returnValue =
                            sendParameter(pCB, kContentLength, NULL);
                    if (returnValue == kDrpServiceOk)
                        returnValue =
                            sendParameter(pCB, kPathTranslated, NULL);
                    if (returnValue == kDrpServiceOk)
                        returnValue =
                            sendParameter(pCB, kQueryString, NULL);
                    if (returnValue == kDrpServiceOk)
                        returnValue =
                            sendParameter(pCB, kRequestURI, NULL);
                    if (returnValue == kDrpServiceOk)
                        returnValue =
                            sendParameter(pCB, kServletPath, NULL);
                    if (returnValue == kDrpServiceOk)
                        returnValue = handleGetAllHeaders(pCB);
                    break;

                case kContentType:
                case kAuthType:
                    /* group 2 */
                    returnValue = sendParameter(pCB, kContentType, NULL);
                    if (returnValue == kDrpServiceOk)
                        returnValue = sendParameter(pCB, kAuthType, NULL);
                    break;

                case kRemoteAddr:
                case kRemoteHost:
                case kRemoteUser:
                    /* group 3 */
                    returnValue = sendParameter(pCB, kRemoteAddr, NULL);
                    if (returnValue == kDrpServiceOk)
                        returnValue =
                            sendParameter(pCB, kRemoteHost, NULL);
                    if (returnValue == kDrpServiceOk)
                        returnValue =
                            sendParameter(pCB, kRemoteUser, NULL);
                    break;

                case kProtocol:
                case kServerName:
                case kServerPort:
                case kScheme:
                    /* group 4 */
                    returnValue = sendParameter(pCB, kProtocol, NULL);
                    if (returnValue == kDrpServiceOk)
                        returnValue =
                            sendParameter(pCB, kServerName, NULL);
                    if (returnValue == kDrpServiceOk)
                        returnValue =
                            sendParameter(pCB, kServerPort, NULL);
                    if (returnValue == kDrpServiceOk)
                        returnValue = sendParameter(pCB, kScheme, NULL);
                    break;

                case kParameters:
                    /* will never be requested by DRP 2.01 server */
                    break;

                default:       /* eh?  what's that? */
                    break;
                }
            } else {
                /* sendParameter() takes care of sending result back to DRP server,
                   excluding final terminating 0 byte; passing kDefinedPlaceholder
                   (an invalid defined string) forces sendParameter to use paramName
                   as its lookup parameter */
                returnValue =
                    sendParameter(pCB, kDefinedPlaceholder, &paramName);
            }
        }

        returnValue = recvByte(pCB, pCB->currentServer->sock, &identDelim);
    }

    /* finish the NV-Pair response */
    if (returnValue == kDrpServiceOk)
        returnValue = sendByte(pCB, pCB->currentServer->sock, &_0);

    if (returnValue == kDrpServiceOk)
        /* force the send */
        returnValue = send_flush(pCB, pCB->currentServer->sock, 0);

    return returnValue;
}


/**
 * handleGetAllHeaders
 * Satisfy the DRP server's request for all Headers.  this routine sends
 *   a single DRP2 name-value pair for each request header; it does _not_
 *   send the terminator for the name-value pair list, so caller must.
 * return: 0 == success; !0 == failure
 */
static DrpServiceStatus handleGetAllHeaders(drpContextBlock * pCB)
{
    int returnValue = kDrpServiceOk;
    drpByte _1 = 1;
    int headerNum = 0;

    do {
        returnValue =
            pCB->getNthHeader(pCB->shellData, headerNum, &pCB->ioBuffer,
                              &pCB->ioBuffer2);
        if ((pCB->ioBuffer.used > 0) && (returnValue == 0)) {
            /* send byte: NV-Pair follows */
            returnValue = sendByte(pCB, pCB->currentServer->sock, &_1);
            if (returnValue == 0) {
                /* send indexed NV pair: ("headername"[headernum], header-name) */
                returnValue =
                    sendIntIndexedPair(pCB, pCB->currentServer->sock,
                                       kHeaderName, headerNum,
                                       pCB->ioBuffer.buffer);
                /* send byte: NV-Pair follows */
                returnValue = sendByte(pCB, pCB->currentServer->sock, &_1);
                if (returnValue == kDrpServiceOk)
                    /* send indexed NV pair: ("headerval"[headernum], header-val) */
                    returnValue =
                        sendIntIndexedPair(pCB, pCB->currentServer->sock,
                                           kHeaderVal, headerNum,
                                           pCB->ioBuffer2.buffer);
            }
        }
        headerNum++;
    } while ((pCB->ioBuffer.used > 0) && (returnValue == kDrpServiceOk));

    /* do _not_ send 0x00 (NV-Pair terminating byte); caller will handle */

    return returnValue;
}


/**
 * handleReadData
 * Satisfy a Read-Data DRP server request in progress.  This routine
 *   receives the maximum requested bytes value (4 bytes) from the drp
 *   server, and sends the data packet with agent data (or failure indicator).
 * return: 0 == success; !0 == failure
 */
static DrpServiceStatus handleReadData(drpContextBlock * pCB)
{
    int returnValue = kDrpServiceOk;
    size_t length;
    drpByte _1 = 1;
    drpByte _0 = 0;
    size_t bytesToPipe = 0;

    if (pCB->serviceStatus < kServicingRequest)
        pCB->serviceStatus = kServicingRequest;
    returnValue =
        recvLong(pCB, pCB->currentServer->sock, (drpInt *) & length);
    if ((length > 0) && (returnValue == kDrpServiceOk)) {
        int result;

        /* NB: don't propagate a non-zero return from readFromAgent as an
           error return from handleReadData (which will be interpreted as a
           DRP-server related error) */

        bytesToPipe = MIN(length, pCB->ioBuffer.avail);
        pCB->ioBuffer.used = 0;
        result = pCB->readFromAgent(pCB->shellData, &pCB->ioBuffer,
                                    bytesToPipe);
        if (result == 0) {
            returnValue = sendByte(pCB, pCB->currentServer->sock, &_1);
            if (returnValue == kDrpServiceOk)
                returnValue = sendLong(pCB, pCB->currentServer->sock,
                                       (drpInt *) & pCB->ioBuffer.used);
            if (returnValue == kDrpServiceOk)
                returnValue = sendBuffer(pCB, pCB->currentServer->sock,
                                         &pCB->ioBuffer);
        } else {
            /* send failure byte */
            sendByte(pCB, pCB->currentServer->sock, &_0);
        }
    } else {
        /* send failure byte */
        sendByte(pCB, pCB->currentServer->sock, &_0);
    }

    if (returnValue == kDrpServiceOk) {
        returnValue = send_flush(pCB, pCB->currentServer->sock, 0);
    }

    return returnValue;
}


/**
 * handleWriteData
 * Satisfy a Write-Data DRP server request in progress.  This routine
 *   receives the data packet (length and data) to be written to the agent
 *   from the DRP server, and attempts to write that data to the agent.
 * return: 0 == success; !0 == failure
 */
static DrpServiceStatus handleWriteData(drpContextBlock * pCB)
{
    int returnValue = kDrpServiceOk;
    size_t length;
    drpByte success = 1;
    size_t bytesPiped = 0;
    size_t bytesToPipe = 0;

    if (pCB->serviceStatus < kServicingRequest)
        pCB->serviceStatus = kServicingRequest;
    returnValue =
        recvLong(pCB, pCB->currentServer->sock, (drpInt *) & length);
    if ((length > 0) && (returnValue == kDrpServiceOk)) {
        if (pCB->statusSetByServer && (!pCB->statusSentToAgent)) {
            returnValue = doSendHeader(pCB);
            /*
             * We must complete the request even if sending the headers fails
             * In particular, we have to read the data from the client or else
             * the protocol will get screwed up.
             */
            if (returnValue != kDrpServiceOk) {
                success = 0;
                returnValue = kDrpServiceOk;
            }
        }
        while ((bytesPiped < length) && (returnValue == kDrpServiceOk)) {
            bytesToPipe = MIN(length - bytesPiped, pCB->ioBuffer.avail);
            returnValue = recvBuffer(pCB, pCB->currentServer->sock,
                                     &pCB->ioBuffer, bytesToPipe);
            if (returnValue == kDrpServiceOk) {
                /* NB: don't propagate a non-zero return from writeToAgent as an
                   error return from handleWriteData (which will be interpreted as
                   a DRP-server related error) */
                int result;

                result = pCB->writeToAgent(pCB->shellData, &pCB->ioBuffer,
                                           bytesToPipe);
                if (result != 0) {
                    success = 0;
                    /*
                     * Return an error so that this gets reported to dynamo
                     * after the request.
                     */
                    returnValue = kDrpServiceError;

                    /*
                     * Close the socket right here.  This will give the immediate
                     * feedback to the DRP thread regardless of whether or not
                     * we are doing "dropConnectionAfterRequest".  It also spares
                     * us from having to finish reading all of the data for this
                     * request (which has been aborted by the user).  It does have
                     * the overhead that we'll need to create a new socket for
                     * the next request but that is not a big hit
                     */
                    closesocket(pCB->currentServer->sock);
                    pCB->currentServer->sock = kInvalidSocket;
                    break;      /* out of while loop */
                }
            } else {
                /* recv (from server) not successful */
                success = 0;
                break;          /* out of while loop */
            }
            bytesPiped += bytesToPipe;
        }
    } else {
        success = 0;
    }

    return returnValue;
}


/**
 * handleFlush
 * Satisfy a Flush-Data-Stream DRP server request in progress.
 * return: 0 == success; !0 == failure
 */
static DrpServiceStatus handleFlush(drpContextBlock * pCB)
{
    int returnValue = kDrpServiceOk;
    drpByte success = 1;
    int result;

    /* NB: don't propagate a non-zero return from flushData as an
       error return from handleFlush (which will be interpreted as a
       DRP-server related error) */

    result = pCB->flushData(pCB->shellData);
    if (result != 0)
        success = 0;

    return returnValue;
}


/**
 * handleClose
 * Satisfy a Close-Data-Stream DRP server request in progress.
 * return: 0 == success; !0 == failure
 */
static DrpServiceStatus handleClose(drpContextBlock * pCB)
{
    int returnValue = kDrpServiceOk;
    drpByte success = 1;
    int result;

    /* NB: don't propagate a non-zero return from closeData as an
       error return from handleClose (which will be interpreted as a
       DRP-server related error) */

    result = pCB->closeData(pCB->shellData);
    if (result != 0)
        success = 0;

    return returnValue;
}


/**
 * handleHeader
 * Satisfy a Send-Header DRP server request in progress.  This routine
 *   (with subroutines) receives status code and message and response 
 *   headers, and distributes headers and status info to agent.
 * return: 0 == success; !0 == failure
 */
static DrpServiceStatus handleHeader(drpContextBlock * pCB)
{
    int returnValue = kDrpServiceOk;
    drpByte shouldSend = 0;
    drpByte success = 1;

    returnValue =
        recvLong(pCB, pCB->currentServer->sock, &pCB->statusCode);
    if (returnValue == kDrpServiceOk) {
        returnValue = recvTLiteral(pCB, pCB->currentServer->sock,
                                   &pCB->statusMesg, &pCB->statusBuffer);
    }
    if (returnValue == kDrpServiceOk)
        returnValue = recvByte(pCB, pCB->currentServer->sock, &shouldSend);

    if (pCB->shellExpectsHeaderPairs) {
        returnValue = dispatchHeaders(pCB);
    } else {
        returnValue = batchHeaders(pCB);
    }

    pCB->statusSetByServer = 1;

    if (shouldSend && (returnValue == kDrpServiceOk)) {
        returnValue = doSendHeader(pCB);
    }

    if (returnValue != kDrpServiceOk)
        success = 0;

    return returnValue;
}


/**
 * dispatchHeaders
 * Retrieve header pairs from DRP server and dispatch them to shell (for
 *   those shells that expect headers to come one pair at a time)
 * return: 0 == success; !0 == failure
 */
static DrpServiceStatus dispatchHeaders(drpContextBlock * pCB)
{
    int returnValue = kDrpServiceOk;
    drpByte pairDelim;

    returnValue = recvByte(pCB, pCB->currentServer->sock, &pairDelim);
    while ((pairDelim == 1) && (returnValue == 0)) {
        drpIdentifier headerName;
        drpTLiteral headerVal;

        pCB->ioBuffer.used = 0; /* "clear" ioBuffer */
        returnValue =
            recvIdentifier(pCB, pCB->currentServer->sock, &headerName,
                           &pCB->ioBuffer);
        if (returnValue == kDrpServiceOk) {
            pCB->ioBuffer2.used = 0;    /* "clear" ioBuffer2 */
            returnValue =
                recvTLiteral(pCB, pCB->currentServer->sock, &headerVal,
                             &pCB->ioBuffer2);
            if (returnValue == kDrpServiceOk) {
                pCB->headers.used = 0;  /* "clear" headers buffer */
                identToAscii(pCB->definedStringLength,
                             pCB->definedStringToAscii, pCB->shellData,
                             &headerName, &pCB->headers);
                pCB->headers2.used = 0; /* "clear" headers2 buffer */
                tliteralToAscii(pCB->definedStringLength,
                                pCB->definedStringToAscii, pCB->shellData,
                                &headerVal, &pCB->headers2);
                returnValue =
                    pCB->setResponseHeader(pCB->shellData, &pCB->headers,
                                           &pCB->headers2);
            }
        }
        if (returnValue == kDrpServiceOk)
            returnValue =
                recvByte(pCB, pCB->currentServer->sock, &pairDelim);
    }

    return returnValue;
}


/**
 * batchHeaders
 * Dump headers from the DRP server into an ascii buffer (for those shells
 *   that expect headers to come in one big lump)
 * return: 0 == success; !0 == failure
 */
static DrpServiceStatus batchHeaders(drpContextBlock * pCB)
{
    int returnValue = kDrpServiceOk;
    drpByte pairDelim;

    returnValue = recvByte(pCB, pCB->currentServer->sock, &pairDelim);
    while ((pairDelim == 1) && (returnValue == kDrpServiceOk)) {
        drpIdentifier headerName;
        drpTLiteral headerVal;

        pCB->ioBuffer.used = 0; /* "clear" ioBuffer */
        returnValue =
            recvIdentifier(pCB, pCB->currentServer->sock, &headerName,
                           &pCB->ioBuffer);
        if (returnValue == kDrpServiceOk) {
            identToAscii(pCB->definedStringLength,
                         pCB->definedStringToAscii, pCB->shellData,
                         &headerName, &pCB->headers);
            concatenate(&pCB->headers, kString_HeaderNVDelim);
            pCB->ioBuffer.used = 0;     /* "clear" ioBuffer */
            returnValue =
                recvTLiteral(pCB, pCB->currentServer->sock, &headerVal,
                             &pCB->ioBuffer);
            if (returnValue == kDrpServiceOk) {
                tliteralToAscii(pCB->definedStringLength,
                                pCB->definedStringToAscii, pCB->shellData,
                                &headerVal, &pCB->headers);
                concatenate(&pCB->headers, kString_Newline);
            }
        }
        returnValue = recvByte(pCB, pCB->currentServer->sock, &pairDelim);
    }
    concatenate(&pCB->headers, kString_Newline);

    return returnValue;
}


/**
 * identToAscii
 * Copy an ascii representation of a DRP Identifier object onto the end
 *   of the supplied buffer.  (Grows buffer as necessary.)
 */
void
identToAscii(DefinedLength_fn iDefinedLength,
             DefinedAscii_fn iDefinedAscii, void *pShellData,
             const drpIdentifier * iIdent, growBuffer * oBuffer)
{
    /* NB: ignoring index */
    tliteralToAscii(iDefinedLength, iDefinedAscii, pShellData,
                    &iIdent->name, oBuffer);
}


/**
 * tliteralToAscii
 * Copy an ascii representation of a DRP Typed Literal object onto the end
 *   of the supplied buffer.  (Grows buffer as necessary.)
 */
void
tliteralToAscii(DefinedLength_fn iDefinedLength,
                DefinedAscii_fn iDefinedAscii, void *pShellData,
                const drpTLiteral * iTLiteral, growBuffer * oBuffer)
{
    size_t result;
    size_t c;

    switch (iTLiteral->type) {
    case kInteger:
        ensureGrowBufferSize_m(oBuffer, (oBuffer->used + 16));
        result = sprintf(oBuffer->buffer + oBuffer->used, "%ld",
                         iTLiteral->value.integer);
        oBuffer->used += result;
        break;
    case kDefined:
        result = iDefinedLength(pShellData, iTLiteral->value.defined);
        if (result > 0) {
            ensureGrowBufferSize_m(oBuffer, (oBuffer->used + result));
        }
        result = sprintf(oBuffer->buffer + oBuffer->used, "%s",
                         iDefinedAscii(pShellData,
                                       iTLiteral->value.defined));
        oBuffer->used += result;
        break;
    case kAscii:
        ensureGrowBufferSize_m(oBuffer,
                               (oBuffer->used + iTLiteral->numChars));
        memmove(oBuffer->buffer + oBuffer->used, iTLiteral->value.ascii,
                iTLiteral->numChars);
        oBuffer->used += iTLiteral->numChars;
        oBuffer->buffer[oBuffer->used] = kChar_Null;
        break;
    case kUnicode:
        ensureGrowBufferSize_m(oBuffer,
                               (oBuffer->used + iTLiteral->numChars));
        for (c = 0; c < iTLiteral->numChars; c++) {
            oBuffer->buffer[oBuffer->used++] =
                ((char *) iTLiteral->value.unicode)[2 * c + 1];
        }
        oBuffer->buffer[oBuffer->used] = kChar_Null;
        break;
    case kNull:
        /* intentional fall-through */
    default:
        break;
    }
}


/**
 * doSendHeader
 * Send HTTP header information to user agent 
 * return: 0 == success; !0 == failure
 */
static DrpServiceStatus doSendHeader(drpContextBlock * pCB)
{
    int returnValue = kDrpServiceOk;

    pCB->statusSentToAgent = 1;

    if (pCB->statusMesg.type > kNull) {
        pCB->ioBuffer.used = 0;
        tliteralToAscii(pCB->definedStringLength,
                        pCB->definedStringToAscii, pCB->shellData,
                        &pCB->statusMesg, &pCB->ioBuffer);
    } else {
        pCB->ioBuffer.buffer[0] = kChar_Null;
        pCB->ioBuffer.used = 0;
    }

    if (pCB->shellExpectsHeaderPairs) {
        returnValue =
            pCB->sendHeaderNoBatch(pCB->shellData, pCB->statusCode,
                                   pCB->ioBuffer.buffer,
                                   pCB->ioBuffer.used);
    } else {
        returnValue = pCB->sendHeaderBatch(pCB->shellData, pCB->statusCode,
                                           pCB->ioBuffer.buffer,
                                           pCB->ioBuffer.used,
                                           pCB->headers.buffer,
                                           pCB->headers.used);
    }

    return returnValue;
}


/**
 * bitcpy32
 * NB: macro, modifies d (destination) in place
 * d: destination (4 bytes)
 * db: destination offset, in bits
 * s: source (4 bytes)
 * sb: source offset, in bits
 * "copies" up to 32 bits from s (offset sb) to d (offset db)
 */
#define bitcpy32(d,db,s,sb)             \
  ((d) |=                               \
   ((db) > (sb) ?                       \
    (((long) (s)) << ((db) - (sb))) :   \
    (((long) (s)) >> ((sb) - (db)))))

/**
 * bitcpy16
 * NB: macro, modifies d (destination) in place
 * d: destination (2 bytes)
 * db: destination offset, in bits
 * s: source (2 bytes)
 * sb: source offset, in bits
 * "copies" up to 16 bits from s (offset sb) to d (offset db)
 */
#define bitcpy16(d,db,s,sb)             \
  ((d) |=                               \
   ((db) > (sb) ?                       \
    (((short) (s)) << ((db) - (sb))) :  \
    (((short) (s)) >> ((sb) - (db)))))


/**
 * getDrpServerInfoFromSid
 * From the session-ID stored in pCB, extract the the address and port
 *   of the DRP server that issued the SID.
 * oAddr: will be filled in with address of DRP server (if return is 0)
 * oPort: will be filled in with port of DRP server (if return is 0)
 * return: 0 == success; !0 == failure
 */
static int
getDrpServerInfoFromSid(drpContextBlock * pCB, unsigned long *oAddr,
                        unsigned short *oPort)
{
    /*
       adapted from Nate-o-rama's documentation of the d3 session id format
       (atg.servlet.sessiontracking.SessionManager):

       The generated id's are based on a byte array with
       the following format:
       bytes 0-3: hash based on counter and start time
       bytes 4-7: counter
       bytes 8-11: host IP addr
       The byte array is then converted to a String.  The byte array is
       broken down into groups of 5 bits, and each group's value (0-31) is
       used to form a character from the character set 'A'-'Z', '0'-'5'.
     */

    int returnValue = kDrpServiceOk;
    char addrInts[8];
    char portInts[4];
    int c;
    char *id = pCB->sessionID;

    /* DRP server IP address:
       convert relevant characters from session ID into array of byte integers */
    for (c = 0; c < 8; c++) {
        if ((id[c + 12] >= 'A') && (id[c + 12] <= 'Z'))
            addrInts[c] = id[c + 12] - 'A';
        else if ((id[c + 12] >= '0') && (id[c + 12] <= '5'))
            addrInts[c] = id[c + 12] - '0' + 26;
        else {
            char badChar[2];
            char buf[128];
            badChar[0] = id[c + 12];
            badChar[1] = kChar_Null;
            sprintf(buf, "(case 1) session id='%s', char='%s'(0x%x)",
                    pCB->sessionID, badChar, (int) id[c + 12]);

            pCB->logMessage(pCB->shellData, kLog_Warn,
                            kLogMessage_BadSIDCharacter, buf, NULL);

            if (pCB->debugEnabled) {
                drpIdentifier cookieHeaderName;
                int result;
                cookieHeaderName.hasIndex = 0;
                cookieHeaderName.name.type = kAscii;
                cookieHeaderName.name.numChars =
                    strlen(kCookieHeaderName) + 1;
                cookieHeaderName.name.value.ascii = kCookieHeaderName;

                result =
                    pCB->getParameter(pCB->shellData, &cookieHeaderName,
                                      &pCB->ioBuffer);
                if (result == 0) {
                    pCB->ioBuffer.buffer[pCB->ioBuffer.used] = kChar_Null;
                    sprintf(buf, "cookie header is='%s'",
                            pCB->ioBuffer.buffer);
                    pCB->logMessage(pCB->shellData, kLog_Info,
                                    kLogMessage_OneString, buf, NULL);
                } else {
                    pCB->logMessage(pCB->shellData, kLog_Info,
                                    kLogMessage_OneString,
                                    "no cookie header present", NULL);
                }
            }
            returnValue = -1;
        }
    }

    /* DRP server port:
       convert relevant characters from session ID into array of byte integers */
    if (returnValue == 0)
        for (c = 0; c < 4; c++) {
            if ((id[c + 19] >= 'A') && (id[c + 19] <= 'Z'))
                portInts[c] = id[c + 19] - 'A';
            else if ((id[c + 19] >= '0') && (id[c + 19] <= '5'))
                portInts[c] = id[c + 19] - '0' + 26;
            else {
                char badChar[2];
                char buf[128];

                badChar[0] = id[c + 19];
                badChar[1] = kChar_Null;
                sprintf(buf, "(case 2) session id='%s', char='%s'(0x%x)",
                        pCB->sessionID, badChar, (int) id[c + 12]);

                pCB->logMessage(pCB->shellData, kLog_Warn,
                                kLogMessage_BadSIDCharacter, badChar,
                                NULL);
                if (pCB->debugEnabled) {
                    drpIdentifier cookieHeaderName;
                    int result;
                    cookieHeaderName.hasIndex = 0;
                    cookieHeaderName.name.type = kAscii;
                    cookieHeaderName.name.numChars =
                        strlen(kCookieHeaderName) + 1;
                    cookieHeaderName.name.value.ascii = kCookieHeaderName;

                    result =
                        pCB->getParameter(pCB->shellData,
                                          &cookieHeaderName,
                                          &pCB->ioBuffer);
                    if (result == 0) {
                        pCB->ioBuffer.buffer[pCB->ioBuffer.used] =
                            kChar_Null;
                        sprintf(buf, "cookie header is='%s'",
                                pCB->ioBuffer.buffer);
                        pCB->logMessage(pCB->shellData, kLog_Info,
                                        kLogMessage_OneString, buf, NULL);
                    } else {
                        pCB->logMessage(pCB->shellData, kLog_Info,
                                        kLogMessage_OneString,
                                        "no cookie header present", NULL);
                    }
                }
                returnValue = kDrpServiceBadSID;
            }
        }

    if (returnValue == kDrpServiceOk) {
        /* DRP server IP address:
           pack bits from drp server int array into *oAddr */
        *oAddr = 0;
        bitcpy32(*oAddr, 31, addrInts[0], 0);
        for (c = 0; c < 6; c++)
            bitcpy32(*oAddr, 30 - c * 5, addrInts[c + 1], 4);
        bitcpy32(*oAddr, 0, addrInts[7], 4);
        /* NB: *oAddr is in host byte-order, because we reconstructed it by
           value rather than receiving it in a specific order; but, it has
           to be in network byte-order for the network calls.  so munge it. */
        *oAddr = htonl(*oAddr);

        /* DRP server port:
           pack bits from drp server int array into *oPort */
        *oPort = 0;
        bitcpy16(*oPort, 15, portInts[0], 3);
        bitcpy16(*oPort, 11, portInts[1], 4);
        bitcpy16(*oPort, 6, portInts[2], 4);
        bitcpy16(*oPort, 1, portInts[3], 4);
        /* NB: *oPort is in host byte-order, because we recontsructed it by
           value rather than receiving it in a specific byte order.  
           so leave it. */
    }

    return returnValue;
}

/* Sets the value of a grow buffer.
 * Return: 0 == success; !0 == failure
 */
int setGrowBuffer(growBuffer * b, const char *s)
{
    clearGrowBuffer_m(b);
    return concatenate(b, s);
}


/**
 * concatenate
 * Concatenate a character string onto the end of pBuffer, growing 
 *   pBuffer if necessary.
 * return: 0 == success; !0 == failure
 */
int concatenate(growBuffer * pBuffer, const char *iString)
{
    int returnValue = kDrpServiceOk;
    int stringLength = strlen(iString);

    ensureGrowBufferSize_m(pBuffer, (pBuffer->used + stringLength + 1));
    pBuffer->buffer[pBuffer->used] = kChar_Null;
    strcat(pBuffer->buffer, iString);
    pBuffer->used += stringLength;

    return returnValue;
}


/**
 * createGrowBuffer
 * creates a new growable buffer structure with at least iInitialSize bytes
 *   bytes allocated to the buffer field.
 * returns: new growBuffer object
 */
growBuffer *createGrowBuffer(growBuffer * pBuffer, size_t iInitialSize)
{
    if (pBuffer != NULL)
        pBuffer->allocated = 0;
    else {
        pBuffer = (growBuffer *) malloc(sizeof(growBuffer));
        if (pBuffer == NULL)
            return NULL;
        pBuffer->allocated = 1;
    }
    pBuffer->used = 0;
    pBuffer->avail = iInitialSize + 1;
    pBuffer->buffer = (char *) malloc(sizeof(char) * pBuffer->avail);
    if (pBuffer->buffer == NULL) {
        if (pBuffer->allocated)
            free(pBuffer);
        return NULL;
    }

    return pBuffer;
}


/**
 * growGrowBuffer
 * increases the allocation of the buffer field of the indicated
 *   growable buffer object by at least iGrowSize bytes.
 * return: 0 == success; !0 == failure (i.e. allocation failure)
 */
int growGrowBuffer(growBuffer * pBuffer, size_t iGrowSize)
{
    int returnValue = kDrpServiceOk;

    pBuffer->avail += iGrowSize;
    pBuffer->buffer = realloc(pBuffer->buffer, pBuffer->avail);
    if (!pBuffer->buffer)
        returnValue = kDrpServiceNoMemory;

    return returnValue;
}

/**
 * destroyGrowBuffer
 * frees up a growBuffer's resources.
 */
void destroyGrowBuffer(growBuffer * pBuffer)
{
    free(pBuffer->buffer);
    if (pBuffer->allocated)
        free(pBuffer);
}

/**
 * hostToInetAddr
 * Given a host address string (either IP address or host name), return
 *   a corresponding internet address value.
 * iHost: host address string (IP or host name).
 * return: internet address value; INADDR_NONE (see DRPTypes.h) on failure.
 */
unsigned long hostToInetAddr(const char *iHost)
{
    unsigned long addr;

    /* first, try inet_addr to see if the host is in dotted-decimal form */
    addr = inet_addr(iHost);
    if (addr == INADDR_NONE) {
        /* second, try gethostbyname */
        struct hostent *he = gethostbyname(iHost);
        struct sockaddr_in sa_in;

        if (he != NULL) {
            memcpy(&sa_in.sin_addr, he->h_addr, he->h_length);
            addr = (unsigned long) sa_in.sin_addr.s_addr;
        }
    }

    return addr;
}

#if 0
/* the following routines were obsoleted with the change from "DRP 2.0"
   to "DRP 2.01" (my fake version numbers) */

/**
 * handleGetAllParameters
 * Satisfy the DRP server's request for all Parameters
 * return: 0 == success; !0 == failure
 */
static DrpServiceStatus handleGetAllParameters(drpContextBlock * pCB)
{
    int returnValue = kDrpServiceOk;
    drpIdentifier paramName;
    int namePtr = 0;
    int valPtr = 0;
    int nextPtr = 0;
    int queryLength = 0;
    drpByte _1 = 1;

    paramName.name.type = kDefined;
    paramName.name.value.defined = kQueryString;
    paramName.hasIndex = 0;

    returnValue =
        pCB->getParameter(pCB->shellData, &paramName, &pCB->ioBuffer2);

    if ((pCB->ioBuffer2.used > 0) && (returnValue == kDrpServiceOk)) {
        queryLength = strlen(pCB->ioBuffer2.buffer);
        while ((nextPtr < queryLength) && (returnValue == kDrpServiceOk)) {
            namePtr = nextPtr;
            if (pCB->ioBuffer2.buffer[namePtr] == kChar_Null)
                break;          /* out of while loop */
            getNextQueryArg(pCB->ioBuffer2.buffer, namePtr, &valPtr,
                            &nextPtr);
            /* send byte: NV-Pair follows */
            returnValue = sendByte(pCB, pCB->currentServer->sock, &_1);
            if (returnValue == 0)
                returnValue =
                    sendNameIndexedPair(pCB, pCB->currentServer->sock,
                                        kParameter,
                                        pCB->ioBuffer2.buffer + namePtr,
                                        pCB->ioBuffer2.buffer + valPtr);
        }
    }

    /* do _not_ send 0x00 (NV-Pair terminating byte); caller will handle */

    return returnValue;
}


/**
 * getNextQueryArg
 * obtain the next query argument from the query argument list
 */
static void
getNextQueryArg(char *pBuffer, int namePtr, int *oValPtr, int *oNextPtr)
{
    char *valCharPtr;
    char *nextCharPtr;

    /* name known to start at namePtr */

    /* first: find where this query argument ends and null-terminate it */
    nextCharPtr = strchr(pBuffer + namePtr, kChar_Ampersand);
    if (!nextCharPtr)
        nextCharPtr = strchr(pBuffer + namePtr, kChar_Null);
    *nextCharPtr = kChar_Null;

    /* second: find next occurrence of '=' */
    valCharPtr = strchr(pBuffer + namePtr, kChar_Equals);
    if (!valCharPtr)
        valCharPtr = nextCharPtr;
    else {
        /* null-terminate the name, and move the val ptr ahead to 1st char of
           value */
        *valCharPtr = kChar_Null;
        valCharPtr++;
    }

    /* now: namePtr indicates 1st char of name, which is null-terminated.
       valCharPtr is either 1st char of value, or null if no value.
       nextCharPtr is terminating char of value (or name, if no value). */

    nextCharPtr++;              /* move next ptr ahead to 1st char of next arg */

    *oValPtr = valCharPtr - pBuffer;
    *oNextPtr = nextCharPtr - pBuffer;
}
#endif
