/*								       HTHost.c
**	REMOTE HOST INFORMATION
**
**	(c) COPYRIGHT MIT 1995.
**	Please first read the full copyright statement in the file COPYRIGH.
**	@(#) $Id: HTHost.c,v 2.45 1998/05/19 16:49:31 frystyk Exp $
**
**	This object manages the information that we know about a remote host.
**	This can for example be what type of host it is, and what version
**	it is using. We also keep track of persistent connections
**
**	April 96  HFN	Written
*/

/* Library include files */
#include "wwwsys.h"
#include "WWWUtil.h"
#include "HTParse.h"
#include "HTAlert.h"
#include "HTError.h"
#include "HTNetMan.h"
#include "HTTrans.h"
#include "HTTPUtil.h"
#include "HTTCP.h"
#include "HTHost.h"					 /* Implemented here */
#include "HTHstMan.h"

#define HOST_TIMEOUT		43200L	     /* Default host timeout is 12 h */

#define TCP_TTL 		600L	     /* Timeout on a busy connection */
#define TCP_IDLE_TTL 		60L	    /* Timeout on an idle connection */

#define MAX_PIPES		50   /* maximum number of pipelined requests */
#define MAX_HOST_RECOVER	3	      /* Max number of auto recovery */
#define DEFAULT_DELAY		30	  /* Default write flush delay in ms */

struct _HTInputStream {
    const HTInputStreamClass *	isa;
};

PRIVATE int HostEvent(SOCKET soc, void * pVoid, HTEventType type);

/* Type definitions and global variables etc. local to this module */
PRIVATE time_t	HostTimeout = HOST_TIMEOUT;	  /* Timeout on host entries */
PRIVATE time_t	TcpTtl = TCP_TTL;          /* Timeout on persistent channels */

PRIVATE HTList	** HostTable = NULL;
PRIVATE HTList * PendHost = NULL;	    /* List of pending host elements */

/* JK: New functions for interruption the automatic pending request 
   activation */
PRIVATE HTHost_ActivateRequestCallback * ActivateReqCBF = NULL;
PRIVATE int HTHost_ActivateRequest (HTNet *net);
PRIVATE BOOL DoPendingReqLaunch = YES; /* controls automatic activation
                                              of pending requests */

PRIVATE int EventTimeout = -1;		        /* Global Host event timeout */

PRIVATE ms_t WriteDelay = DEFAULT_DELAY;		      /* Delay in ms */

PRIVATE int MaxPipelinedRequests = MAX_PIPES;

/* ------------------------------------------------------------------------- */

PRIVATE void free_object (HTHost * me)
{
    if (me) {
	int i;
	HT_FREE(me->hostname);
	HT_FREE(me->type);
	HT_FREE(me->server);
	HT_FREE(me->user_agent);
	HT_FREE(me->range_units);

	/* Delete the channel (if any) */
	if (me->channel) {
	    HTChannel_delete(me->channel, HT_OK);
	    me->channel = NULL;
	}

	/* Unregister the events */
	for (i = 0; i < HTEvent_TYPES; i++)
	    HTEvent_delete(me->events[i]);

	/* Delete the timer (if any) */
	if (me->timer) HTTimer_delete(me->timer);

	/* Delete the queues */
	HTList_delete(me->pipeline);
	HTList_delete(me->pending);
	HT_FREE(me);
    }
}

PRIVATE BOOL delete_object (HTList * list, HTHost * me)
{
    if (CORE_TRACE) HTTrace("Host info... object %p from list %p\n", me, list);
    HTList_removeObject(list, (void *) me);
    free_object(me);
    return YES;
}

PRIVATE BOOL isLastInPipe (HTHost * host, HTNet * net)
{
    return HTList_lastObject(host->pipeline) == net;
}

/*
**	HostEvent - host event manager - recieves events from the event 
**	manager and dispatches them to the client net objects by calling the 
**	net object's cbf.
**
*/
PRIVATE int HostEvent (SOCKET soc, void * pVoid, HTEventType type)
{
    HTHost * host = (HTHost *)pVoid;

    if (type == HTEvent_READ || type == HTEvent_CLOSE) {
	HTNet * targetNet;

	/* call the first net object */
	do {
	    int ret;
	    targetNet = (HTNet *)HTList_firstObject(host->pipeline);
	    if (targetNet) {
		if (CORE_TRACE)
		    HTTrace("Host Event.. READ passed to `%s\'\n", 
			    HTAnchor_physical(HTRequest_anchor(HTNet_request(targetNet))));
		if ((ret = (*targetNet->event.cbf)(HTChannel_socket(host->channel), 
						  targetNet->event.param, type)) != HT_OK)
		    return ret;
	    }
	    if (targetNet == NULL && host->remainingRead > 0) {
		if (CORE_TRACE)
		    HTTrace("HostEvent... Error: %d bytes left to read and nowhere to put them\n",
			    host->remainingRead);
		host->remainingRead = 0;
		/*
		**	Fall through to close the channel
		*/
	    }
	/* call pipelined net object to eat all the data in the channel */
	} while (host->remainingRead > 0);

	/* last target net should have set remainingRead to 0 */
	if (targetNet)
	    return HT_OK;

	/* If there was notargetNet, it should be a close */
	if (CORE_TRACE)
	    HTTrace("Host Event.. host %p `%s\' closed connection.\n", 
		    host, host->hostname);

	/* Is there garbage in the channel? Let's check: */
	{
	    char buf[256];
	    int ret;
	    memset(buf, '\0', sizeof(buf));
	    while ((ret = NETREAD(HTChannel_socket(host->channel), buf, sizeof(buf))) > 0) {
		if (CORE_TRACE)
		    HTTrace("Host Event.. Host %p `%s\' had %d extraneous bytes: `%s\'\n",
			    host, host->hostname, ret, buf);
		memset(buf, '\0', sizeof(buf));		
	    }	    
	}
	HTHost_clearChannel(host, HT_OK);
	return HT_OK; 	     /* extra garbage does not constitute an application error */
	
    } else if (type == HTEvent_WRITE || type == HTEvent_CONNECT) {
	HTNet * targetNet = (HTNet *)HTList_lastObject(host->pipeline);
	if (targetNet) {
	    if (CORE_TRACE)
		HTTrace("Host Event.. WRITE passed to `%s\'\n", 
			HTAnchor_physical(HTRequest_anchor(HTNet_request(targetNet))));
	    return (*targetNet->event.cbf)(HTChannel_socket(host->channel), targetNet->event.param, type);
	}
	HTTrace("Host Event.. Who wants to write to `%s\'?\n", host->hostname);
	return HT_ERROR;
    } else if (type == HTEvent_TIMEOUT) {

	if (CORE_TRACE)
	    HTTrace("Host Event.. WE SHOULD DELETE ALL REQUEST ON `%s\'?\n",
		    host->hostname);

    } else {
	HTTrace("Don't know how to handle OOB data from `%s\'?\n", 
		host->hostname);
    }
    return HT_OK;
}

PRIVATE int TimeoutEvent (HTTimer * timer, void * param, HTEventType type)
{
    HTHost * host = (HTHost *) param;
    SOCKET sockfd = HTChannel_socket(host->channel);
    int result = HostEvent (sockfd, host, HTEvent_CLOSE);
    HTTimer_delete(timer);
    host->timer = NULL;
    return result;
}

/*
**	Search the host info cache for a host object or create a new one
**	and add it. Examples of host names are
**
**		www.w3.org
**		www.foo.com:8000
**		18.52.0.18
**
**	Returns Host object or NULL if error. You may get back an already
**	existing host object - you're not guaranteed a new one each time.
*/
PUBLIC HTHost * HTHost_new (char * host, u_short u_port)
{
    HTList * list = NULL;			    /* Current list in cache */
    HTHost * pres = NULL;
    int hash = 0;
    if (!host) {
	if (CORE_TRACE) HTTrace("Host info... Bad argument\n");
	return NULL;
    }
    
    /* Find a hash for this host */
    {
	char *ptr;
	for (ptr=host; *ptr; ptr++)
	    hash = (int) ((hash * 3 + (*(unsigned char *) ptr)) % HOST_HASH_SIZE);
	if (!HostTable) {
	    if ((HostTable = (HTList **) HT_CALLOC(HOST_HASH_SIZE,
						   sizeof(HTList *))) == NULL)
	        HT_OUTOFMEM("HTHost_find");
	}
	if (!HostTable[hash]) HostTable[hash] = HTList_new();
	list = HostTable[hash];
    }

    /* Search the cache */
    {
	HTList * cur = list;
	while ((pres = (HTHost *) HTList_nextObject(cur))) {
	    if (!strcmp(pres->hostname, host) && u_port == pres->u_port) {
		if (HTHost_isIdle(pres) && time(NULL)>pres->ntime+HostTimeout){
		    if (CORE_TRACE)
			HTTrace("Host info... Collecting host info %p\n",pres);
		    delete_object(list, pres);
		    pres = NULL;
		}
		break;
	    }
	}
    }

    /* If not found then create new Host object, else use existing one */
    if (pres) {
	if (pres->channel) {

            /*
            **  If we have a TTL for this TCP connection then
            **  check that we haven't passed it.
            */
            if (pres->expires > 0) {
                time_t t = time(NULL);
                if (pres->expires < t) {	   /* Cached channel is cold */
                    if (CORE_TRACE)
                        HTTrace("Host info... Persistent channel %p gotten cold\n",
                        pres->channel);
                    HTChannel_delete(pres->channel, HT_OK);
                    pres->channel = NULL;
                } else {
                    pres->expires = t + TcpTtl;
                    if (CORE_TRACE)
                        HTTrace("Host info... REUSING CHANNEL %p\n",pres->channel);
                }
            }
	}
    } else {
	if ((pres = (HTHost *) HT_CALLOC(1, sizeof(HTHost))) == NULL)
	    HT_OUTOFMEM("HTHost_add");
	pres->hash = hash;
	StrAllocCopy(pres->hostname, host);
	pres->u_port = u_port;
	pres->ntime = time(NULL);
	pres->mode = HT_TP_SINGLE;
	pres->delay = WriteDelay;
	{
	    int i;
	    for (i = 0; i < HTEvent_TYPES; i++)
		pres->events[i]= HTEvent_new(HostEvent, pres, HT_PRIORITY_MAX, EventTimeout);
	}
	if (CORE_TRACE) 
	    HTTrace("Host info... added `%s\' with host %p to list %p\n",
		    host, pres, list);
	HTList_addObject(list, (void *) pres);
    }
    return pres;
}

PUBLIC HTHost * HTHost_newWParse (HTRequest * request, char * url, u_short u_port)
{
    char * port;
    char * fullhost = NULL;
    char * parsedHost = NULL;
    SockA * sin;
    HTHost * me;
    char * proxy = HTRequest_proxy(request);

    fullhost = HTParse(proxy ? proxy : url, "", PARSE_HOST);

	      /* If there's an @ then use the stuff after it as a hostname */
    if (fullhost) {
	char * at_sign;
	if ((at_sign = strchr(fullhost, '@')) != NULL)
	    parsedHost = at_sign+1;
	else
	    parsedHost = fullhost;
    }
    if (!parsedHost || !*parsedHost) {
	HTRequest_addError(request, ERR_FATAL, NO, HTERR_NO_HOST,
			   NULL, 0, "HTDoConnect");
	HT_FREE(fullhost);
	return NULL;
    }
    port = strchr(parsedHost, ':');
    if (PROT_TRACE)
	HTTrace("HTDoConnect. Looking up `%s\'\n", parsedHost);
    if (port) {
	*port++ = '\0';
	if (!*port || !isdigit((int) *port))
	    port = 0;
	u_port = (u_short) atol(port);
    }
    /* Find information about this host */
    if ((me = HTHost_new(parsedHost, u_port)) == NULL) {
	if (PROT_TRACE)HTTrace("HTDoConnect. Can't get host info\n");
	me->tcpstate = TCP_ERROR;
	return NULL;
    }
    sin = &me->sock_addr;
    memset((void *) sin, '\0', sizeof(SockA));
#ifdef DECNET
    sin->sdn_family = AF_DECnet;
    net->sock_addr.sdn_objnum = port ? (unsigned char)(strtol(port, (char **) 0, 10)) : DNP_OBJ;
#else  /* Internet */
    sin->sin_family = AF_INET;
    sin->sin_port = htons(u_port);
#endif
    HT_FREE(fullhost);	/* parsedHost points into fullhost */
    return me;
}

/*
**	Search the host info cache for a host object. Examples of host names:
**
**		www.w3.org
**		www.foo.com:8000
**		18.52.0.18
**
**	Returns Host object or NULL if not found.
*/
PUBLIC HTHost * HTHost_find (char * host)
{
    HTList * list = NULL;			    /* Current list in cache */
    HTHost * pres = NULL;
    if (CORE_TRACE)
	HTTrace("Host info... Looking for `%s\'\n", host ? host : "<null>");

    /* Find a hash for this host */
    if (host && HostTable) {
	int hash = 0;
	char *ptr;
	for (ptr=host; *ptr; ptr++)
	    hash = (int) ((hash * 3 + (*(unsigned char *) ptr)) % HOST_HASH_SIZE);
	if (!HostTable[hash]) return NULL;
	list = HostTable[hash];

	/* Search the cache */
	{
	    HTList * cur = list;
	    while ((pres = (HTHost *) HTList_nextObject(cur))) {
		if (!strcmp(pres->hostname, host)) {
		    if (time(NULL) > pres->ntime + HostTimeout) {
			if (CORE_TRACE)
			    HTTrace("Host info... Collecting host %p\n", pres);
			delete_object(list, pres);
			pres = NULL;
  		    } else {
			if (CORE_TRACE)
			    HTTrace("Host info... Found `%s\'\n", host);
		    }
		    return pres;
		}
	    }
	}
    }
    return NULL;
}

/*
**	Get and set the hostname of the remote host
*/
PUBLIC char * HTHost_name (HTHost * host)
{
     return host ? host->hostname : NULL;
}

/*
**	Get and set the type class of the remote host
*/
PUBLIC char * HTHost_class (HTHost * host)
{
     return host ? host->type : NULL;
}

PUBLIC void HTHost_setClass (HTHost * host, char * s_class)
{
    if (host && s_class) StrAllocCopy(host->type, s_class);
}

/*
**	Get and set the version of the remote host
*/
PUBLIC int HTHost_version (HTHost *host)
{
     return host ? host->version : 0;
}

PUBLIC void HTHost_setVersion (HTHost * host, int version)
{
    if (host) host->version = version;
}

/*
**	Get and set the cache timeout for persistent entries.
**	The default value is TCP_TIMEOUT
*/
PUBLIC void HTHost_setPersistTimeout (time_t timeout)
{
    TcpTtl = timeout;
}

PUBLIC time_t HTHost_persistTimeout (time_t timeout)
{
    return TcpTtl;
}

/*	Persistent Connection Expiration
**	--------------------------------
**	Should normally not be used. If, then use calendar time.
*/
PUBLIC void HTHost_setPersistExpires (HTHost * host, time_t expires)
{
    if (host) host->expires = expires;
}

PUBLIC time_t HTHost_persistExpires (HTHost * host)
{
    return host ? host->expires : -1;
}

PUBLIC void HTHost_setReqsPerConnection (HTHost * host, int reqs)
{
    if (host) host->reqsPerConnection = reqs;
}

PUBLIC int HTHost_reqsPerConnection (HTHost * host)
{
    return host ? host->reqsPerConnection : -1;
}

PUBLIC void HTHost_setReqsMade (HTHost * host, int reqs)
{
    if (host) host->reqsMade = reqs;
}

PUBLIC int HTHost_reqsMade (HTHost * host)
{
    return host ? host->reqsMade : -1;
}

/*
**	Public methods for this host
*/
PUBLIC HTMethod HTHost_publicMethods (HTHost * me)
{
    return me ? me->methods : METHOD_INVALID;
}

PUBLIC void HTHost_setPublicMethods (HTHost * me, HTMethod methodset)
{
    if (me) me->methods = methodset;
}

PUBLIC void HTHost_appendPublicMethods (HTHost * me, HTMethod methodset)
{
    if (me) me->methods |= methodset;
}

/*
**	Get and set the server name of the remote host
*/
PUBLIC char * HTHost_server (HTHost * host)
{
     return host ? host->server : NULL;
}

PUBLIC BOOL HTHost_setServer (HTHost * host, const char * server)
{
    if (host && server) {
	StrAllocCopy(host->server, server);
	return YES;
    }
    return NO;
}

/*
**	Get and set the userAgent name of the remote host
*/
PUBLIC char * HTHost_userAgent (HTHost * host)
{
     return host ? host->user_agent : NULL;
}

PUBLIC BOOL HTHost_setUserAgent (HTHost * host, const char * userAgent)
{
    if (host && userAgent) {
	StrAllocCopy(host->user_agent, userAgent);
	return YES;
    }
    return NO;
}

/*
**	Get and set acceptable range units
*/
PUBLIC char * HTHost_rangeUnits (HTHost * host)
{
     return host ? host->range_units : NULL;
}

PUBLIC BOOL HTHost_setRangeUnits (HTHost * host, const char * units)
{
    if (host && units) {
	StrAllocCopy(host->range_units, units);
	return YES;
    }
    return NO;
}

/*
**	Checks whether a specific range unit is OK. We always say
**	YES except if we have a specific statement from the server that
**	it doesn't understand byte ranges - that is - it has sent "none"
**	in a "Accept-Range" response header
*/
PUBLIC BOOL HTHost_isRangeUnitAcceptable (HTHost * host, const char * unit)
{
    if (host && unit) {
#if 0
	if (host->range_units) {
	    char * start = strcasestr(host->range_units, "none");

	    /*
	    **  Check that "none" is infact a token. It could be part of some
	    **  other valid string, so we'd better check for it.
	    */
	    if (start) {
		
		
	    }
	    return NO;
	}
#endif
	return strcasecomp(unit, "bytes") ? NO : YES;
    }
    return NO;
}

/*	HTHost_catchClose
**	-----------------
**	This function is registered when the socket is idle so that we get
**	a notification if the socket closes at the other end. At this point
**	we can't use the request object as it might have been freed a long
**	time ago.
*/
PUBLIC int HTHost_catchClose (SOCKET soc, void * context, HTEventType type)
{
    HTNet * net = (HTNet *)context;
    HTHost * host = net->host;
    if (CORE_TRACE)
	HTTrace("Catch Close. called with socket %d with type %x\n",
		soc, type);
    if (type == HTEvent_READ) {
	HTChannel * ch = HTChannel_find(soc);	  /* Find associated channel */
	HTHost * host = HTChannel_host(ch);	      /* and associated host */
	if (ch && host) {	    
	    if (CORE_TRACE) HTTrace("Catch Close. CLOSING socket %d\n", soc);
	    HTHost_clearChannel(host, HT_OK);
	} else {
	    if (CORE_TRACE) HTTrace("Catch Close. socket %d NOT FOUND!\n",soc);
	}
    }
    HTHost_unregister(host, net, HTEvent_CLOSE);
    return HT_OK;
}

/*
**	As soon as we know that this host accepts persistent connections,
**	we associated the channel with the host. 
**	We don't want more than MaxSockets-2 connections to be persistent in
**	order to avoid deadlock.
*/
PUBLIC BOOL HTHost_setPersistent (HTHost *		host,
				  BOOL			persistent,
				  HTTransportMode	mode)
{
    if (!host) return NO;

    if (!persistent) {
	/*
	**  We use the HT_IGNORE status code as we don't want to free
	**  the stream at this point in time. The situation we want to
	**  avoid is that we free the channel from within the stream pipe.
	**  This will lead to an infinite look having the stream freing
	**  itself.
	*/
	host->persistent = NO;
	return HTHost_clearChannel(host, HT_IGNORE);
    }

    /*
    ** Set the host persistent if not already. Also update the mode to
    ** the new one - it may have changed
    */
    HTHost_setMode(host, mode);
    if (!host->persistent) {
	SOCKET sockfd = HTChannel_socket(host->channel);
	if (sockfd != INVSOC && HTNet_availablePersistentSockets() > 0) {
	    host->persistent = YES;
	    host->expires = time(NULL) + TcpTtl;     /* Default timeout */
	    HTChannel_setHost(host->channel, host);
	    HTNet_increasePersistentSocket();
	    if (CORE_TRACE)
		HTTrace("Host info... added host %p as persistent\n", host);
	    return YES;
	} else {
	    if (CORE_TRACE)
		HTTrace("Host info... no room for persistent socket %d\n",
			sockfd);
	    return NO;
	}
    } else {
	if (CORE_TRACE) HTTrace("Host info... %p already persistent\n", host);
	return YES;
    }
    return NO;
}

/*
**	Check whether we have a persistent channel or not
*/
PUBLIC BOOL HTHost_isPersistent (HTHost * host)
{
    return host && host->persistent;
}

/*
**	Find persistent channel associated with this host.
*/
PUBLIC HTChannel * HTHost_channel (HTHost * host)
{
    return host ? host->channel : NULL;
}


/*
**  Check whether we have got a "close" notification, for example in the
**  connection header
*/
PUBLIC BOOL HTHost_setCloseNotification (HTHost * host, BOOL mode)
{
    if (host) {
	host->close_notification = mode;
	return YES;
    }
    return NO;
}

PUBLIC BOOL HTHost_closeNotification (HTHost * host)
{
    return host && host->close_notification;
}

/*
**	Clear the persistent entry by deleting the channel object. Note that
**	the channel object is only deleted if it's not used anymore.
*/
PUBLIC BOOL HTHost_clearChannel (HTHost * host, int status)
{
    if (host && host->channel) {
	HTChannel_setHost(host->channel, NULL);
	
	HTEvent_unregister(HTChannel_socket(host->channel), HTEvent_READ);
	HTEvent_unregister(HTChannel_socket(host->channel), HTEvent_WRITE);
	host->registeredFor = 0;

	/*
	**  We don't want to recursively delete ourselves so if we are
	**  called from within the stream pipe then don't delete the channel
	**  at this point
	*/
	HTChannel_delete(host->channel, status);
	host->expires = 0;	
	host->channel = NULL;
	host->tcpstate = TCP_BEGIN;
	host->reqsMade = 0;
	if (HTHost_isPersistent(host)) {
	    HTNet_decreasePersistentSocket();
	    host->persistent = NO;
	}
	host->close_notification = NO;
	host->broken_pipe = NO;
       	host->mode = HT_TP_SINGLE;

	if (CORE_TRACE) HTTrace("Host info... removed host %p as persistent\n", host);

	if (!HTList_isEmpty(host->pending)) {
	    if (CORE_TRACE)
		HTTrace("Host has %d object(s) pending - attempting launch\n", HTList_count(host->pending));
	    HTHost_launchPending(host);
	}
	return YES;
    }
    return NO;
}

PUBLIC BOOL HTHost_doRecover (HTHost * host)
{
    return host ? host->do_recover : NO;
}

/*
**	Move all entries in the pipeline and move the rest to the pending
**	queue. They will get launched at a later point in time.
*/
PUBLIC BOOL HTHost_recoverPipe (HTHost * host)
{
    if (host) {
	int piped = HTList_count(host->pipeline);
	if (piped > 0) {
	    int cnt;
	    host->recovered++;
	    if (CORE_TRACE)
		HTTrace("Host recovered %d times. Moving %d Net objects from pipe line to pending queue\n",
			host->recovered, piped);
	    
	    /*
	    **  Unregister this host for all events
	    */
	    HTEvent_unregister(HTChannel_socket(host->channel), HTEvent_READ);
	    HTEvent_unregister(HTChannel_socket(host->channel), HTEvent_WRITE);
	    host->registeredFor = 0;

	    /*
	    **  Set new mode to single until we know what is going on
	    */
	    host->mode = HT_TP_SINGLE;

	    /*
	    **  Move all net objects from the net object to the pending queue.
	    */
	    if (!host->pending) host->pending = HTList_new();
	    for (cnt=0; cnt<piped; cnt++) {
		HTNet * net = HTList_removeLastObject(host->pipeline);
		if (CORE_TRACE) HTTrace("Host recover Resetting net object %p\n", net);
		net->registeredFor = 0;
		(*net->event.cbf)(HTChannel_socket(host->channel), net->event.param, HTEvent_RESET);
		HTList_appendObject(host->pending, net);
	    }

	    HTChannel_setSemaphore(host->channel, 0);
	    HTHost_clearChannel(host, HT_INTERRUPTED);
	    host->do_recover = NO;
	}
	return YES;
    }
    return NO;
}

/*
**	Handle the connection mode. The mode may change mode in the 
**	middle of a connection.
*/
PUBLIC HTTransportMode HTHost_mode (HTHost * host, BOOL * active)
{
    return host ? host->mode : HT_TP_SINGLE;
}

/*
**	If the new mode is lower than the old mode then adjust the pipeline
**	accordingly. That is, if we are going into single mode then move
**	all entries in the pipeline and move the rest to the pending
**	queue. They will get launched at a later point in time.
*/
PUBLIC BOOL HTHost_setMode (HTHost * host, HTTransportMode mode)
{
    if (host) {
	/*
	**  Check the new mode and see if we must adjust the queues.
	*/
	if (mode == HT_TP_SINGLE && host->mode > mode) {
	    int piped = HTList_count(host->pipeline);
	    if (piped > 0) {
		int cnt;
		if (CORE_TRACE)
		    HTTrace("Host info... Moving %d Net objects from pipe line to pending queue\n", piped);
		if (!host->pending) host->pending = HTList_new();
		for (cnt=0; cnt<piped; cnt++) {
		    HTNet * net = HTList_removeLastObject(host->pipeline);
		    if (CORE_TRACE) HTTrace("Host info... Resetting net object %p\n", net);
		    (*net->event.cbf)(HTChannel_socket(host->channel), net->event.param, HTEvent_RESET);
		    HTList_appendObject(host->pending, net);
		}
		HTChannel_setSemaphore(host->channel, 0);
		HTHost_clearChannel(host, HT_INTERRUPTED);
	    }
	}

	/*
	**  If we know that this host is bad then we don't allow anything than
	**  single mode. We can't recover connections for the rest of our life
	*/
	if (mode == HT_TP_PIPELINE && host->recovered > MAX_HOST_RECOVER) {
	    if (PROT_TRACE)
		HTTrace("Host info... %p is bad for pipelining so we won't do it!!!\n",
			host);
	} else {
	    host->mode = mode;
	    if (PROT_TRACE)
		HTTrace("Host info... New mode is %d for host %p\n", host->mode, host);
	}
    }
    return NO;
}

/*
**	Check whether a host is idle meaning if it is ready for a new
**	request which depends on the mode of the host. If the host is 
**	idle, i.e. ready for use then return YES else NO. If the host supports
**	persistent connections then still only return idle if no requests are
**	ongoing. 
*/
PUBLIC BOOL HTHost_isIdle (HTHost * host)
{
    return (host && HTList_isEmpty(host->pipeline));
}

PRIVATE BOOL _roomInPipe (HTHost * host)
{
    int count;
    if (!host ||
	(host->reqsPerConnection && host->reqsMade >= host->reqsPerConnection) ||
	HTHost_closeNotification(host) || host->broken_pipe)
	return NO;
    count = HTList_count(host->pipeline);
    switch (host->mode) {
    case HT_TP_SINGLE:
	return count <= 0;
    case HT_TP_PIPELINE:
	return count < MaxPipelinedRequests;
    case HT_TP_INTERLEAVE:
	return YES;
    }
    return NO;
}

/*
**	Add a net object to the host object. If the host
**	is idle then add to active list (pipeline) else add
**	it to the pending list
**	Return HT_PENDING if we must pend, HT_OK, or HT_ERROR
*/
PUBLIC int HTHost_addNet (HTHost * host, HTNet * net)
{
    if (host && net) {
	int status = HT_OK;
	BOOL doit = (host->doit==net);

	/*
	**  If we don't have a socket already then check to see if we can get
	**  one. Otherwise we put the host object into our pending queue.
	*/	
	if (!host->channel && HTNet_availableSockets() <= 0) {
	    if (!PendHost) PendHost = HTList_new();
	    if (CORE_TRACE)
		HTTrace("Host info... Add Host %p as pending\n", host);
	    HTList_addObject(PendHost, host);
	    status = HT_PENDING;
	}

#if 0
	/*
	** First check whether the net object is already on either queue.
	** Do NOT add extra copies of the HTNet object to
	** the pipeline or pending list (if it's already on the list).
	*/
	if (HTList_indexOf(host->pipeline, net) >= 0) {
	    if (CORE_TRACE)
		HTTrace("Host info... The Net %p (request %p) is already in pipe,"
			" %d requests made, %d requests in pipe, %d pending\n",
			net, net->request, host->reqsMade,
			HTList_count(host->pipeline),
			HTList_count(host->pending));
	    HTDebugBreak(__FILE__, __LINE__,
			 "Net object %p registered multiple times in pipeline\n",
			 net);
	    return HT_OK;
	}

	if (HTList_indexOf(host->pending,  net) >= 0) {
	    if (CORE_TRACE)
		HTTrace("Host info... The Net %p (request %p) already pending,"
			" %d requests made, %d requests in pipe, %d pending\n",
			net, net->request, host->reqsMade,
			HTList_count(host->pipeline),
			HTList_count(host->pending));
	    HTDebugBreak(__FILE__, __LINE__,
			 "Net object %p registered multiple times in pending queue\n",
			 net);

	    return HT_PENDING;
	}
#endif

	/*
	**  Add net object to either active or pending queue.
	*/
	if (_roomInPipe(host) && (HTList_isEmpty(host->pending) || doit)) {
	    if (doit) host->doit = NULL;
	    if (!host->pipeline) host->pipeline = HTList_new();
	    HTList_addObject(host->pipeline, net);
	    host->reqsMade++;
            if (CORE_TRACE)
		HTTrace("Host info... Add Net %p (request %p) to pipe, %d requests made, %d requests in pipe, %d pending\n",
			net, net->request, host->reqsMade, HTList_count(host->pipeline), HTList_count(host->pending));

	    /*
	    **  If we have been idle then make sure we delete the timer
	    */
	    if (host->timer) {
		HTTimer_delete(host->timer);
		host->timer = NULL;
	    }
           
            /*JK: New CBF function
	    ** Call any user-defined callback to say the request will
            ** be processed.
            */
            HTHost_ActivateRequest (net);

	} else {
	    if (!host->pending) host->pending = HTList_new();
	    HTList_addObject(host->pending, net);	    
	    if (CORE_TRACE)
		HTTrace("Host info... Add Net %p (request %p) to pending, %d requests made, %d requests in pipe, %d pending\n",
			net, net->request, host->reqsMade, HTList_count(host->pipeline), HTList_count(host->pending));
	    status = HT_PENDING;
	}
	return status;
    }
    return HT_ERROR;
}

PUBLIC BOOL HTHost_free (HTHost * host, int status)
{
    if (host->channel) {

	/* Check if we should keep the socket open */
        if (HTHost_isPersistent(host)) {
            if (HTHost_closeNotification(host)) {
		int piped = HTList_count(host->pipeline);
		if (CORE_TRACE)
		    HTTrace("Host Object. got close notifiation on socket %d\n",
			    HTChannel_socket(host->channel));
                
		/*
		**  If more than a single element (this one) in the pipe
		**  then we have to recover gracefully
		*/
		if (piped > 1) {
		    host->reqsPerConnection = host->reqsMade - piped;
		    if (CORE_TRACE)
			HTTrace("%d requests made, %d in pipe, max %d requests pr connection\n",
				host->reqsMade, piped, host->reqsPerConnection);
		    host->do_recover = YES;
		    HTChannel_delete(host->channel, status);
		} else {
		    HTChannel_setSemaphore(host->channel, 0);
		    HTHost_clearChannel(host, status);
		}
	    } else if ((HTList_count(host->pipeline) <= 1 &&
			host->reqsMade == host->reqsPerConnection)) {
                if (CORE_TRACE) HTTrace("Host Object. closing persistent socket %d\n",
					HTChannel_socket(host->channel));
                
                /* 
                **  By lowering the semaphore we make sure that the channel
                **  is gonna be deleted
                */
                HTChannel_setSemaphore(host->channel, 0);
                HTHost_clearChannel(host, status);

            } else {
                if (CORE_TRACE) HTTrace("Host Object. keeping persistent socket %d\n", HTChannel_socket(host->channel));
                HTChannel_delete(host->channel, status);
                
                /*
                **  If connection is idle then set a timer so that we close the 
                **  connection if idle too long
                */
                if (HTHost_isIdle(host) && HTList_isEmpty(host->pending) && !host->timer) {
                    host->timer = HTTimer_new(NULL, TimeoutEvent,
					      host, TCP_IDLE_TTL, YES, YES);
                    if (PROT_TRACE) HTTrace("Host........ Object %p going idle...\n", host);
                }
            }
            return YES;
        } else {
            if (CORE_TRACE) HTTrace("Host Object. closing socket %d\n", HTChannel_socket(host->channel));
	    HTHost_clearChannel(host, status);
        }
    }
    return NO;
}

PUBLIC BOOL HTHost_deleteNet (HTHost * host, HTNet * net)
{
    if (host && net) {
        if (CORE_TRACE) HTTrace("Host info... Remove %p from pipe\n", net);
	HTList_removeObjectAll(host->pipeline, net);
	HTList_removeObjectAll(host->pending, net); /* just to make sure */
	return YES;
    }
    return NO;
}

/*
**	Handle pending host objects.
**	There are two ways we can end up with pending reqyests:
**	 1) If we are out of sockets then register new host objects as pending.
**	 2) If we are pending on a connection then register new net objects as
**	    pending
**	This set of functions handles pending host objects and can start new
**	requests as resources get available
*/

/*
**	Check this host object for any pending requests and return the next
**	registered Net object.
*/
PUBLIC HTNet * HTHost_nextPendingNet (HTHost * host)
{
    HTNet * net = NULL;
    if (host && host->pending) {
	/*JK 23/Sep/96 Bug correction. Associated the following lines to the
	**above if. There was a missing pair of brackets. 
	*/
	if ((net = (HTNet *) HTList_removeFirstObject(host->pending)) != NULL) {
	    if (CORE_TRACE)
		HTTrace("Host info... Popping %p from pending net queue\n", net);
#if 0
	    {
		HTRequest * request = HTNet_request(net);
		char * uri = HTAnchor_address((HTAnchor *) HTRequest_anchor(request));
		fprintf(stderr, "Popping '%s'\n", uri);
	    }
#endif
	    host->doit = net;
	}
    }
    return net;
}

/*
**	Return the current list of pending host objects waiting for a socket
*/
PUBLIC HTHost * HTHost_nextPendingHost (void)
{
    HTHost * host = NULL;
    if (PendHost) {
	if ((host = (HTHost *) HTList_removeFirstObject(PendHost)) != NULL)
	    if (PROT_TRACE)
		HTTrace("Host info... Popping %p from pending host queue\n",
			host);
    }
    return host;
}

/*
**	Start the next pending request if any. First we look for pending
**	requests for the same host and then we check for any other pending
**	hosts
*/
PUBLIC BOOL HTHost_launchPending (HTHost * host)
{
    int available = HTNet_availableSockets();

    if (!host) {
	if (PROT_TRACE) HTTrace("Host info... Bad arguments\n");
	return NO;
    }

    /*
    **  Check if we do have resources available for a new request
    **  This can either be reusing an existing connection or opening a new one
    */
    if (available > 0 || host->mode >= HT_TP_PIPELINE) {
	HTNet * net;

	/*
	**  In pipeline we can only have one doing writing at a time.
	**  We therefore check that there are no other Net object
	**  registered for write
	*/
	if (host->mode == HT_TP_PIPELINE) {
	    net = (HTNet *) HTList_lastObject(host->pipeline);
	    if (net && net->registeredFor == HTEvent_WRITE)
		return NO;
	}

	/*
	**  Check the current Host object for pending Net objects
	**
	**  Send out as many as will fit in pipe.
	*/
	while (_roomInPipe(host) && (net = HTHost_nextPendingNet(host))) {
	    int status = NO;
	    /* JK: Added new code to interrupt pending requests*/
	    if (DoPendingReqLaunch) {
	      HTHost_ActivateRequest (net);
	      if (CORE_TRACE)
		HTTrace("Launch pending net object %p with %d reqs in pipe (%d reqs made)\n", net, HTList_count(host->pipeline), host->reqsMade);
	      status = HTNet_execute(net, HTEvent_WRITE);
            } else
	      {
		/* replace the object that was taken off the list */
		HTList_appendObject (host->pending, (void *) net);
		/* go the the other pending hosts routine */
		break;
	      }

	    if (status != HT_OK)
		return status;
        }

	/*
	**  Check for other pending Host objects
	*/
	{
	    HTHost * pending = HTHost_nextPendingHost();
	    if (pending) {
		HTNet * net = HTHost_nextPendingNet(pending);
		if (net) {
                   if (DoPendingReqLaunch) {
                       HTHost_ActivateRequest (net);
                       return HTNet_execute(net, HTEvent_WRITE);
                   } else {
		     /* replace the object that was taken off the list */
		     HTList_appendObject (PendHost, (void *) pending);
                     return NO;
		   }
		}
	    }
	}
    } else
	if (PROT_TRACE) HTTrace("Host info... No more requests.\n");
    return NO;
}

PUBLIC HTNet * HTHost_firstNet (HTHost * host)
{
    return (HTNet *) HTList_firstObject(host->pipeline);
}

/*
**	The host event manager keeps track of the state of it's client engines
**	(typically HTTPEvent), accepting multiple blocks on read or write from
**	multiple pipelined engines. It then registers its own engine 
**	(HostEvent) with the event manager.
*/
PUBLIC int HTHost_connect (HTHost * host, HTNet * net, char * url, HTProtocolId port)
{
#if 1
    HTRequest * request = HTNet_request(net);
    int status;
    if (!host) {
	HTProtocol * protocol = HTNet_protocol(net);
	if ((host = HTHost_newWParse(request, url, HTProtocol_id(protocol))) == NULL)
	    return HT_ERROR;
	if (!host->channel) {
	    host->forceWriteFlush = YES;
	    host->lock = net;
	}
	HTNet_setHost(net, host);
    }

    if (!host->lock || (host->lock && host->lock == net)) {
	status = HTDoConnect(net, url, port);
	if (status == HT_OK) {
	    host->lock = NULL;
	    return HT_OK;
	}
	if (status == HT_WOULD_BLOCK) {
	    host->lock = net;
	    return HT_WOULD_BLOCK;
	}
	if (status == HT_PENDING) return HT_WOULD_BLOCK;
    } else {
	if ((status = HTHost_addNet(host, net)) == HT_PENDING) {
	    return HT_PENDING;
	}
    }
    return HT_ERROR; /* @@@ - some more deletion and stuff here? */
#else
    int status;
    status = HTDoConnect(net, url, port);
    if (status == HT_OK) return HT_OK;
    if (status == HT_WOULD_BLOCK || status == HT_PENDING)
	return HT_WOULD_BLOCK;
    return HT_ERROR; /* @@@ - some more deletion and stuff here? */
#endif
}

/*
**	Rules: SINGLE: one element in pipe, either reading or writing
**		 PIPE: n element in pipe, n-1 reading, 1 writing
*/
PUBLIC int HTHost_register (HTHost * host, HTNet * net, HTEventType type)
{
  HTEvent *event;

    if (host && net) {

	if (type == HTEvent_CLOSE) {

	    /*
	    **  Unregister this host for all events
	    */
	    HTEvent_unregister(HTChannel_socket(host->channel), HTEvent_READ);
	    HTEvent_unregister(HTChannel_socket(host->channel), HTEvent_WRITE);
	    host->registeredFor = 0;
	    return YES;

	} else {

	    /* net object may already be registered */
	    if (HTEvent_BITS(type) & net->registeredFor)
		return NO;
	    net->registeredFor ^= HTEvent_BITS(type);

	    /* host object may already be registered */
	    if (host->registeredFor & HTEvent_BITS(type))
		return YES;
	    host->registeredFor ^= HTEvent_BITS(type);
	    /* JK:  register a request in the event structure */
	    event =  *(host->events+HTEvent_INDEX(type));
	    event->request = HTNet_request (net);
	    return HTEvent_register(HTChannel_socket(host->channel),
				    type, event);
	}
    }
    return NO;
}

PUBLIC int HTHost_unregister (HTHost * host, HTNet * net, HTEventType type)
{
    if (host && net) {

	/* net object may not be registered */
	if (!(HTEvent_BITS(type) & net->registeredFor))
	    return NO;
	net->registeredFor ^= HTEvent_BITS(type);

	/* host object may not be registered */
	if (!(host->registeredFor & HTEvent_BITS(type)))
	    return YES;
	host->registeredFor ^= HTEvent_BITS(type);

	/* stay registered for READ to catch a socket close */
	/* WRITE and CONNECT can be unregistered, though */
	if ((type == HTEvent_WRITE && isLastInPipe(host, net)) || 
	    type == HTEvent_CONNECT)
	    /* if we are blocked downstream, shut down the whole pipe */
	    HTEvent_unregister(HTChannel_socket(host->channel), type);
	return YES;
    }
    return NO;
}

/*
**	The reader tells HostEvent that it's stream did not finish the data
*/
PUBLIC BOOL HTHost_setRemainingRead (HTHost * host, size_t remaining)
{
    if (host == NULL) return NO;
    host->remainingRead = remaining;
    if (PROT_TRACE) HTTrace("Host........ %d bytes remaining \n", remaining);
    if (host->broken_pipe && remaining == 0) {
	if (PROT_TRACE) HTTrace("Host........ Emtied out connection\n");
    }
    return YES;
}

PUBLIC size_t HTHost_remainingRead (HTHost * host)
{
    return host ? host->remainingRead : -1;
}

PUBLIC SockA * HTHost_getSockAddr (HTHost * host)
{
    if (!host) return NULL;
    return &host->sock_addr;
}

PUBLIC BOOL HTHost_setHome (HTHost * host, int home)
{
    if (!host) return NO;
    host->home = home;
    return YES;
}

PUBLIC int HTHost_home (HTHost * host)
{
    if (!host) return 0;
    return host->home;
}

PUBLIC BOOL HTHost_setRetry (HTHost * host, int retry)
{
    if (!host) return NO;
    host->retry = retry;
    return YES;
}

PUBLIC BOOL HTHost_decreaseRetry (HTHost * host)
{
    if (!host) return NO;

    if (host->retry > 0) host->retry--;
    return YES;

}

PUBLIC int HTHost_retry (HTHost * host)
{
    if (!host) return 0;
    return host->retry;
}

#if 0	/* Is a macro right now */
PRIVATE BOOL HTHost_setDNS5 (HTHost * host, HTdns * dns)
{
    if (!host) return NO;
    host->dns = dns;
    return YES;
}
#endif

PUBLIC BOOL HTHost_setChannel (HTHost * host, HTChannel * channel)
{
    if (!host) return NO;
    host->channel = channel;
    return YES;
}

PUBLIC HTNet * HTHost_getReadNet(HTHost * host)
{
    return host ? (HTNet *) HTList_firstObject(host->pipeline) : NULL;
}

PUBLIC HTNet * HTHost_getWriteNet(HTHost * host)
{
    return host ? (HTNet *) HTList_lastObject(host->pipeline) : NULL;
}

/*
**	Create the input stream and bind it to the channel
**	Please read the description in the HTIOStream module for the parameters
*/
PUBLIC HTInputStream * HTHost_getInput (HTHost * host, HTTransport * tp,
					void * param, int mode)
{
    if (host && host->channel && tp) {
	HTChannel * ch = host->channel;
	HTInputStream * input = (*tp->input_new)(host, ch, param, mode);
	HTChannel_setInput(ch, input);
	return HTChannel_getChannelIStream(ch);
    }
    if (CORE_TRACE) HTTrace("Host Object. Can't create input stream\n");
    return NULL;
}

PUBLIC HTOutputStream * HTHost_getOutput (HTHost * host, HTTransport * tp,
					  void * param, int mode)
{
    if (host && host->channel && tp) {
	HTChannel * ch = host->channel;
	HTOutputStream * output = (*tp->output_new)(host, ch, param, mode);
	HTChannel_setOutput(ch, output);
	return output;
    }
    if (CORE_TRACE) HTTrace("Host Object. Can't create output stream\n");
    return NULL;
}

PUBLIC HTOutputStream * HTHost_output (HTHost * host, HTNet * net)
{
    if (host && host->channel && net) {
	HTOutputStream * output = HTChannel_output(host->channel);
	return output;
    }
    return NULL;
}

PUBLIC int HTHost_read(HTHost * host, HTNet * net)
{
    HTInputStream * input = HTChannel_input(host->channel);
    if (net != HTHost_getReadNet(host)) {
	HTHost_register(host, net, HTEvent_READ);
	return HT_WOULD_BLOCK;
    }

    /*
    **  If there is no input channel then this can either mean that
    **  we have lost the channel or an error occurred. We return
    **  HT_CLOSED as this is a sign to the caller that we don't 
    **  have a channel
    */
    return input ? (*input->isa->read)(input) : HT_CLOSED;
}

PUBLIC BOOL HTHost_setConsumed(HTHost * host, size_t bytes)
{
    HTInputStream * input;
    if (!host || !host->channel) return NO;
    if ((input = HTChannel_input(host->channel)) == NULL)
	return NO;
    if (CORE_TRACE)
	HTTrace("Host........ passing %d bytes as consumed to %p\n", bytes, input);
    return (*input->isa->consumed)(input, bytes);
}

PUBLIC int HTHost_hash (HTHost * host)
{
    return host ? host->hash : -1;
}

PUBLIC BOOL HTHost_setWriteDelay (HTHost * host, ms_t delay)
{
    if (host && delay >= 0) {
	host->delay = delay;
	return YES;
    }
    return NO;
}

PUBLIC ms_t HTHost_writeDelay (HTHost * host)
{
    return host ? host->delay : 0;
}

PUBLIC int HTHost_findWriteDelay (HTHost * host, ms_t lastFlushTime, int buffSize)
{
#if 0
    unsigned short mtu;
    int ret = -1;
    int socket = HTChannel_socket(host->channel);
#ifndef WWW_MSWINDOWS
    ret = ioctl(socket, 666, (unsigned long)&mtu);
#endif /* WWW_MSWINDOWS */
    if ((ret == 0 && buffSize >= mtu) || host->forceWriteFlush)
	return 0;
    return host->delay;
#else
    return host->forceWriteFlush ? 0 : host->delay;
#endif
}

PUBLIC BOOL HTHost_setDefaultWriteDelay (ms_t delay)
{
    if (delay >= 0) {
	WriteDelay = delay;
	if (CORE_TRACE) HTTrace("Host........ Default write delay is %d ms\n", delay);
	return YES;
    }
    return NO;
}

PUBLIC ms_t HTHost_defaultWriteDelay (void)
{
    return WriteDelay;
}

PUBLIC int HTHost_forceFlush(HTHost * host)
{
    HTNet * targetNet = (HTNet *) HTList_lastObject(host->pipeline);
    int ret;
    if (targetNet == NULL) return HT_ERROR;
    if (CORE_TRACE)
	HTTrace("Host Event.. FLUSH passed to `%s\'\n", 
		HTAnchor_physical(HTRequest_anchor(HTNet_request(targetNet))));
    host->forceWriteFlush = YES;
    ret = (*targetNet->event.cbf)(HTChannel_socket(host->channel), targetNet->event.param, HTEvent_FLUSH);
    host->forceWriteFlush = NO;
    return ret;
}

/*
** Context pointer to be used as a user defined context 
*/
PUBLIC void HTHost_setContext (HTHost * me, void * context)
{
  if (me) me->context = context;
}

PUBLIC void * HTHost_context (HTHost * me)
{
  return me ? me->context : NULL;
}

PUBLIC int HTHost_eventTimeout (void)
{
    return EventTimeout;
}

PUBLIC void HTHost_setEventTimeout (int millis)
{
    EventTimeout = millis;
    if (CORE_TRACE) HTTrace("Host........ Setting event timeout to %d ms\n", millis);
}

PUBLIC BOOL HTHost_setMaxPipelinedRequests (int max)
{
    if (max > 1) {
	MaxPipelinedRequests = max;
	return YES;
    }
    return NO;
}

PUBLIC int HTHost_maxPipelinedRequests (void)
{
    return MaxPipelinedRequests;
}

PUBLIC void HTHost_setActivateRequestCallback (HTHost_ActivateRequestCallback * cbf)
{
    if (CORE_TRACE) HTTrace("HTHost...... Registering %p\n", cbf);
    ActivateReqCBF = cbf;
}

PRIVATE int HTHost_ActivateRequest (HTNet * net)
{
    HTRequest * request = NULL;
    if (!ActivateReqCBF) {
	if (CORE_TRACE)
	    HTTrace("HTHost...... No ActivateRequest callback handler registered\n");
	return HT_ERROR;
    }
    request = HTNet_request(net);
    return (*ActivateReqCBF)(request);
}

PUBLIC void HTHost_disable_PendingReqLaunch (void)
{
    DoPendingReqLaunch = NO;
}

PUBLIC void HTHost_enable_PendingReqLaunch (void)
{
    DoPendingReqLaunch = YES;
}