/*<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.h - public API for DRP client routines
 */

#ifndef _DRP_CLIENT_H_
#define _DRP_CLIENT_H_

#include "DRPTypes.h"

#ifdef _CPOOL
#include "connectionPool.h"
#endif

/* constants */

/* orig. size of main (per-context) buffers */
#define kMainBufferSize    4096

/* orig. size of minor storage buffers */
#define kMinorBufferSize   1024

/* max # of DRP servers for which the CM will keep information */
#define kMaxDRPRecords 100

/* length of session ID */
#define kMaxSessionIDLength 256

/* size of IO buffers used for buffered send/recv */
#define kSockBufSize 4200

/* default total time to go around a loop calling connect() and
 * getting a timeout 
 */
#define kDRP_DefaultMaxConnectTimeout  30  /* seconds */

/* checkpoints in the service of a request */
enum {
  kIdle                  = 0,

  kConnectingToServer,
  kExaminingRequest,
  kNotificationInProgress,
  kServicingRequest,

  kServiceComplete,
  kNotificationComplete,

  kServiceError,

  kServiceStatusPlaceholder
};

/* status values returned from drpService */
typedef enum DrpServiceStatus {
  kDrpServiceOk = 0,        /* service request succeeded */
  kDrpServiceError = -1,    /* service request failed nonrecoverably */
  kDrpServiceNoServer = -2, /* service request failed because no server
			     * could satisfy it. */
  kDrpServiceFailover = -3, /* load-managed service request failed; failover
			     * should be attempted */
  kDrpServiceNoMemory = -4, /* service request failed due to lack of server
			     * memory */
  kDrpServiceProtocolError = -5, /* service request failed due to a protocol
				  * error */
  kDrpServiceBadSID = -6,   /* service request failed due to bad SID */
} DrpServiceStatus;

/* HTTP response information for kDrpServiceFailover handling */
#define kFailoverStatusCode 302 /* Moved Temporarily */
#define kFailoverStatusMessage "Application failure, use alternate server"

void drpSetFailoverServer(const char* newServer);

void drpSetSessionCookieName(const char* cookieName);

/* type definitions */

typedef struct _sockBuf sockBuf;

/* SockBuf: IO buffer used for buffered send/recv */
struct _sockBuf {
  char * data;
  size_t capacity;   /* # bytes allocated to data field */
  size_t used;       /* # bytes of data in data field */
  size_t curPtr;     /* indicates next byte in buffer to "recv" */
};


typedef struct _drpContextBlock drpContextBlock;
typedef struct _drpServerRecord drpServerRecord;


/* DRPServerRecord: maintains information about a single DRP2 server */
struct _drpServerRecord {
  socket_t sock;               /* socket connection to the DRP2 server */
  unsigned long inetaddr;      /* IP address of DRP2 server */
  unsigned short port;         /* port of DRP2 server */
  unsigned short probability;  /* weight used to determine selection of
				  DRP2 server from list; see 
				  DRPClient.c:drpService_load() */
  unsigned short flags;        /* per-server flags, e.g. "active" */
#ifdef _CPOOL
  connectionPool *pool;        /* per-server connection pool */
#endif  
};


/** SHELL CALLBACK FUNCTION TYPE DEFINITIONS **/
/* these prototypes indicate the callback interface used by the DRP2 client
   to access functionality in the CM "shell" specific to each server API */

/**
 * GetParameter_fn
 * Retrieve the value (in ascii) for a request parameter (e.g. URI, 
 *   server port, etc).
 * pShellData: shell-specific (request-specific) data object
 * iName: incdicates parameter to retrieve
 * oVal: (growable) buffer in which to store parameter value (in ascii)
 *       (if no value found, return successfully with empty oVal buffer)
 * return: 0 == success; !0 == failure (other than val not found)
 */
typedef int (* GetParameter_fn) 
     (void * pShellData , const drpIdentifier * iName, growBuffer * oVal);

/**
 * GetNthHeader_fn
 * Retrieve the name and value (in ascii) of the nth request header.
 * pShellData: shell-specific (request-specific) data object
 * iHeaderNum: 0-based index of header to retrieve
 * oName, oVal: (growable) buffers in which to store header name and value
 *        (in ascii) (if no header for iHeaderNum, return successfully
 *        with empty name and val buffers)
 * return: 0 == success; !0 == failure (other than no such header)
 */
typedef int (* GetNthHeader_fn)
     (void * pShellData, int iHeaderNum, growBuffer * oName, 
      growBuffer * oVal);

/**
 * ReadFromAgent_fn
 * Read at most iMaxLength bytes from the user agent (browser).
 * pShellData: shell-specific (request-specific) data object
 * oBuffer: buffer in which to store data read from browser; this callback
 *          should not increase the size available in oBuffer
 * iMaxLength: maximum number of bytes to read from agent; actual number of
 *             bytes read is indicated via oBuffer->used.  guaranteed to be
 *             <= bytes available in oBuffer.
 * return: 0 == success (including EOF); !0 == failure (other than EOF)
 * note: if the data stream to the agent has been closed (physically or
 *       logically), return failure.
 */
typedef int (* ReadFromAgent_fn)
     (void * pShellData, growBuffer * oBuffer, size_t iMaxLength);

/**
 * WriteToAgent_fn
 * Write iLength bytes to the user agent (browser).
 * pShellData: shell-specific (request-specific) data object
 * iBuffer: contains data to write to agent
 * iLength: number of bytes to write to agent.
 * return: 0 == success (all bytes were written); !0 == failure
 * note: if the data stream to the agent has been closed (physically or
 *       logically), return failure.
 */
typedef int (* WriteToAgent_fn)
     (void * pShellData, const growBuffer * iBuffer, size_t iLength);

/**
 * FlushData_fn
 * Flush the data stream to the user agent (browser), if possible.
 * pShellData: shell-specific (request-specific) data object
 * note: if the data stream to the agent has been closed (physically or
 *       logically), return failure.
 */
typedef int (* FlushData_fn) 
     (void * pShellData);

/**
 * CloseData_fn
 * Close the data stream to the user agent (browser), if possible.
 * pShellData: shell-specific (request-specific) data object
 * note: if it is not possible or advisable for the CM shell to close the
 *       data stream, it is permissible to mark it as closed for the
 *       purpose of responding to future callbacks as if the stream were
 *       closed.
 */
typedef int (* CloseData_fn)
     (void * pShellData);

/**
 * SendHeaderBatch_fn
 * Send HTTP response status and header(s) to user agent (browser);
 *   headers are provided in a preformatted block.
 * pShellData: shell-specific (request-specific) data object
 * iStatusCode: HTTP status code (e.g. 200)
 * iStatusMesg: optional status message for HTTP status line
 * iMesgLength: number of relevant bytes in iStatusMesg; test iMesgLength
 *              rather than iStatusMesg for presence of message
 * iHeaders: preformatted block of header name-value pairs, including
 *           newlines (should need no parsing by shell)
 * iHeadersLength: number of relevant bytes in iHeaders
 * return: 0 == success; !0 == failure
 * note: a CM shell implementation need implement only one of 
 *       SendHeaderBatch or SendHeaderNoBatch; the shell's expectation is
 *       noted in the DRP Context Block passed to drpService.  a shell
 *       that registers itself as expecting batched headers need not
 *       implement a SetResponseHeader callback.
 */
typedef int (* SendHeaderBatch_fn)
     (void * pShellData, drpInt iStatusCode, const char * iStatusMesg, 
      size_t iMesgLength, const char * iHeaders, size_t iHeadersLength);

/**
 * SetResponseHeader_fn
 * Set a single HTTP response header name-value pair, to be sent to the agent
 *   when SendHeaderNoBatch is invoked.
 * pShellData: shell-specific (request-specific) data object
 * iHeaderName: name of response header (ascii, null-terminated)
 * iHeaderValue: value of response header (ascii, null-terminated)
 * return: 0 == success; !0 == failure
 * note: a shell that registers itself as expecting batched headers need not
 *       implement a SetResponseHeader callback.
 */
typedef int (* SetResponseHeader_fn)
     (void * pShellData, const growBuffer * iHeaderName, 
      const growBuffer * iHeaderValue);

/**
 * SendHeaderNoBatch_fn
 * Send HTTP response status and header(s) to user agent (browser);
 *   headers will have been provided already via SetResponseHeader callback,
 *   and must be sent with this callback.
 * pShellData: shell-specific (request-specific) data object
 * iStatusCode: HTTP status code (e.g. 200)
 * iStatusMesg: optional status message for HTTP status line
 * iMesgLength: number of relevant bytes in iStatusMesg; test iMesgLength
 *              rather than iStatusMesg for presence of message
 * return: 0 == success; !0 == failure
 * note: a CM shell implementation need implement only one of 
 *       SendHeaderBatch or SendHeaderNoBatch; the shell's expectation is
 *       noted in the DRP Context Block passed to drpService.  a shell
 *       that registers itself as expecting batched headers need not
 *       implement a SetResponseHeader callback.
 */
typedef int (* SendHeaderNoBatch_fn)
     (void * pShellData, drpInt iStatusCode, const char * iStatusMesg, 
      size_t iMesgLength);

/**
 * DefinedLength_fn
 * Return the length of the shell-specific ascii representation of the
 *   indicated DRP defined string.
 * pShellData: shell-specific (request-specific) data object
 * iDefined: DRP defined string String-ID.
 * return: length of shell's representation of iDefined; 0 if the shell has
 *         no representation for iDefined
 */
typedef int (* DefinedLength_fn)
     (void * pShellData, drpByte iDefined);

/**
 * DefinedAscii_fn
 * Return the (null-terminated) shell-specific ascii representation of the
 *   indicated DRP defined string.
 * pShellData: shell-specific (request-specific) data object
 * iDefined: DRP defined string String-ID.
 * return: shell's representation of iDefined; NULL if the shell has no
 *         representation for iDefined.
 */
typedef const char * (* DefinedAscii_fn)
     (void * pShellData, drpByte iDefined);

/**
 * LogMessage_fn
 * Log the message associated with iMessageNumber (with up to two optional
 *   arguments) to the HTTP-server's log (or WIN32 event log for ISAPI).
 * pShellData: shell-specific (request-specific) data object
 * iMessageType: indicates whether message is "Info", "Warning", or "Error"
 * iMessageNumber: index of message to log
 * iParam1, iParam2: optional parameters to include in the log message (can be
 *                   NULL)
 */
typedef void (* LogMessage_fn)
     (void * pShellData, int iMessageType, int iMessageNumber, 
      const char * iParam1, const char * iParam2);

/* log message types */
enum {
  kLog_Info,
  kLog_Warn,
  kLog_Error
};


/* structure definitions */

/* DRPContextBlock: maintains the information each service "context" 
   (service thread for multithreaded servers or service process for
   multiprocess servers) needs to service requests; in the multithreaded
   case, an instance of this structure is associated with each service
   thread via the Thread-Local-Storage mechanism (see threadabstr.h) */
struct _drpContextBlock {
  /** INPUTS **/

  /* call back functions (for accessing shell functionality) */
  GetParameter_fn getParameter;
  GetNthHeader_fn getNthHeader;
  ReadFromAgent_fn readFromAgent;
  WriteToAgent_fn writeToAgent;
  FlushData_fn flushData;
  CloseData_fn closeData;
  SendHeaderBatch_fn sendHeaderBatch;
  SendHeaderNoBatch_fn sendHeaderNoBatch;
  SetResponseHeader_fn setResponseHeader;
  DefinedLength_fn definedStringLength;
  DefinedAscii_fn definedStringToAscii;
  LogMessage_fn logMessage;

  /* 0: CM shell expects response headers to be sent in one big block;
     !0: CM shell expects response headers in name-value pairs.
     see DRPClient.c:handleHeader*/
  short shellExpectsHeaderPairs;

  /* shellData: http-server-API-specific chunk of data the CM shell passes in;
     to be sent back to shell in shell callbacks */
  void * shellData;


  /** INTERNALS **/

  int serviceStatus;

  /* socket IO buffers */
  sockBuf recvBuf;
  sockBuf sendBuf;

  growBuffer ioBuffer;
  growBuffer ioBuffer2;

  drpServerRecord * currentServer;
  drpServerRecord drpServers[kMaxDRPRecords];
  drpServerRecord * drpServerPtrs[kMaxDRPRecords];
  int numDrpServers;
  unsigned int drpServerInfoVersion;  /* local DRP information version;
					 compared to 
					 CMLDRP.h:gCMLDRP_InfoVersion */

  short statusSetByServer;  /* 0: DRP server has not yet set HTTP status */
  short statusSentToAgent;
  drpInt statusCode;        /* HTTP response code */
  drpTLiteral statusMesg;   /* HTTP response message (or kNull) */
  growBuffer headers;
  growBuffer headers2;
  growBuffer statusBuffer;

  char sessionID[kMaxSessionIDLength];  /* SID (if any) for current requests */

  unsigned int totalProb;  /* total of "probabilities" (weights) for all 
			      known DRP2 servers */

  char isLoggedRequest; /* 0 for regular service request, 1 for logged request*/

  char debugEnabled; /* 1 if debugging is turned on */
  char isTargetedRequest; /* 1 if dyn_server arg was specified */

  int maxConnectTimeout; /* total time (seconds) to go around a loop
                            calling connect() and getting a timeout */

  /*
   * The address and port of the DRP server if isTargetedRequest is
   * true
   */
  unsigned long targetedServerAddr;
  unsigned short targetedServerPort;
};



/* clearGrowBuffer: (_m : reminds me this is a macro :)
   reset growbuffer to 0 bytes used; set first byte to null character 
     (in case buffer about to be used as C string) */
#define clearGrowBuffer_m(/* (growBuffer *) */b)       \
  { (b)->used = 0;                                     \
    (b)->buffer[0] = kChar_Null; }

/* ensureGrowBufferSize: (_m : reminds me this is a macro :)
   if bytes allocated to growbuffer less than bytes requested, buffer is
     grown (by realloc()) to requested size */
#define ensureGrowBufferSize_m(/* (growBuffer *) */ b, /* size_t */ s)      \
  { if ((b)->avail < (s))                                                   \
      growGrowBuffer((b), (s) - (b)->avail + 1); }

/* sets the value of a grow buffer
 */
int setGrowBuffer(growBuffer* b, const char* s);

/* DRP-type to ASCII string converters */
void identToAscii(DefinedLength_fn iDefinedLength, 
		  DefinedAscii_fn iDefinedAscii, void * pShellData, 
		  const drpIdentifier * iIdent, growBuffer * oBuffer);
void tliteralToAscii(DefinedLength_fn iDefinedLength, 
		     DefinedAscii_fn iDefinedAscii, void * pShellData, 
		     const drpTLiteral * iTLiteral, growBuffer * oBuffer);

/* growbuffer manipulation */
growBuffer * createGrowBuffer(growBuffer * pBuffer, size_t iInitialSize);
int concatenate(growBuffer * pBuffer, const char * iString);
int growGrowBuffer(growBuffer * pBuffer, size_t iGrowSize);
void destroyGrowBuffer(growBuffer * growBuffer);


/* DRP2 client service entry points */
void drpInitialize(void);
void drpReset(drpContextBlock * pCB);
int drpService(drpContextBlock * pCB);


/* convert host identifier (IP address or host name) to struct in_addr */
unsigned long hostToInetAddr(const char * iHost);


#endif  /* _DRP_CLIENT_H_ */
