/*     
 * $RCSfile: chan.c,v $
 *
 * x-kernel v3.3
 *
 * Copyright (c) 1993,1991,1990,1996  Arizona Board of Regents
 *
 * $Log: chan.c,v $
 * Revision 1.3  1996/01/29 22:11:44  slm
 * Updated copyright and version.
 *
 * Revision 1.2  1995/08/03  19:15:57  davidm
 * 64-bit cleanup and x3.2->x3.3 conversion.
 *
 * Revision 1.1  1995/07/28  22:10:52  slm
 * Initial revision
 *
 * Revision 1.59.2.1.1.2  1994/12/02  18:10:05  hkaram
 * Changed to new mapResolve interface
 * Changed to msgPeek/Poke
 * Added casts
 *
 * Revision 1.59.2.1.1.1  1994/11/23  17:43:24  hkaram
 * New branch
 *
 * Revision 1.59.2.1  1994/04/25  21:29:18  menze
 * timeout code was resending to the wrong session and dereferencing
 * null pointers.
 *
 * Revision 1.59  1993/12/13  22:37:47  menze
 * Modifications from UMass:
 *
 *   [ 93/09/21          nahum ]
 *   Got rid of casts in mapUnbind, mapResolve calls
 */

#include "xkernel.h"
#include "chan_internal.h"
#include "bidctl.h"


/* If NO_KILLTICKET is defined, CHAN will not inform the lower protocol when it
 * can free a message.  The lower protocol will then free messages based on
 * its timeouts.
 */
/* #define NO_KILLTICKET */

#define TIMEOUT_TRACE	TR_SOFT_ERRORS

/*
 * Global data
 */
int 		tracechanp=1;

typedef struct {
    Protl	self;
    IPhost	peer;
} DisableStubInfo;


#ifdef __STDC__

static XkReturn	chanBidctlUnregister( Protl, IPhost * );
static int	chanControlProtl( Protl, int, char *, int );
static XkReturn	chanDemux( Protl, Sessn, Msg * );
static long	chanHdrLoad( void *, char *, long, void * );
static void	disableStub( Event, VOID * );
static int	getIdleSeqNum( Map, ActiveID * );
static void 	getProcProtl( Protl );
static Sessn	getSpecIdleSessn( Protl, Enable *, ActiveID *, Map, Map );
static void 	killticket( Sessn);

#else

static XkReturn	chanBidctlUnregister();
static void	disableStub();
static int	getIdleSeqNum();
static void 	getProcProtl();
static Sessn	getSpecIdleSessn();
static void 	killticket();

#endif /* __STDC__ */



#define HDR ((CHAN_HDR *)hdr)

/*
 * chanHdrStore - Used when calling msgPush
 */
void
chanHdrStore(hdr, dst, len, arg)
    VOID *hdr;
    char *dst;
    long int len;
    VOID *arg;
{
    CHAN_HDR h;
    
    xAssert(len == CHANHLEN);  
    h.chan = htons(HDR->chan);
    h.flags = HDR->flags;
    h.prot_id = htonl(HDR->prot_id);
    h.seq = htonl(HDR->seq);
    h.len = htonl(HDR->len);
    bcopy((char *)(&h), dst, CHANHLEN);
}


/*
 * chanHdrLoad - Used when calling msgPop
 */
static long
chanHdrLoad(hdr, src, len, arg)
    VOID *hdr;
    char *src;
    long int len;
    VOID *arg;
{
    xAssert(len == sizeof(CHAN_HDR));  
    bcopy(src, (char *)hdr, CHANHLEN);
    HDR->chan = ntohs(HDR->chan);
    HDR->prot_id = ntohl(HDR->prot_id);
    HDR->seq = ntohl(HDR->seq);
    HDR->len = ntohl(HDR->len);
    return CHANHLEN;
}

#undef HDR

  
void
chanEventFlush(s)
    Sessn s;
{
    CHAN_STATE	*state = (CHAN_STATE *)s->state;

    if ( state->event ) {
	evCancel(state->event);
	state->event = 0;
    }
}


/*
 * chan_init
 */
void
chan_init(self)
    Protl self;
{
    Part	part;
    PSTATE 	*pstate;
    
    xTrace0(chanp, TR_GROSS_EVENTS, "CHAN init");
    
    if (!xIsProtl(xGetProtlDown(self, 0))) {
	xTrace0(chanp, TR_ERRORS,
		"CHAN could not get get transport protl -- not initializing");
	return;
    }
    if (!xIsProtl(xGetProtlDown(self, CHAN_BIDCTL_I))) {
	xTrace0(chanp, TR_ERRORS,
		"CHAN could not get get bidctl protl -- not initializing");
	return;
    }
    self->state = (VOID *)(pstate = X_NEW(PSTATE));
    getProcProtl(self);
    pstate->idleSvrMap =
      			mapCreate(CHAN_IDLE_SERVER_MAP_SZ, sizeof(IPhost));
    pstate->idleCliMap =
      			mapCreate(CHAN_IDLE_CLIENT_MAP_SZ, sizeof(IPhost));
    pstate->actSvrKeyMap =
      			mapCreate(CHAN_ACTIVE_SERVER_MAP_SZ, sizeof(ActiveID));
    pstate->actSvrHostMap =
      			mapCreate(CHAN_ACTIVE_SERVER_MAP_SZ, sizeof(IPhost));
    pstate->actCliKeyMap =
		      	mapCreate(CHAN_ACTIVE_CLIENT_MAP_SZ, sizeof(ActiveID));
    pstate->actCliHostMap =
      			mapCreate(CHAN_ACTIVE_CLIENT_MAP_SZ, sizeof(IPhost));
    pstate->passiveMap = mapCreate(CHAN_HLP_MAP_SZ, sizeof(PassiveID));
    pstate->newChanMap = mapCreate(CHAN_HLP_MAP_SZ, sizeof(IPhost));
    semInit(&pstate->newSessnLock, 1);
    
    partInit(&part, 1);
    partPush(part, ANY_HOST, 0);
    if ( xOpenEnable(self, self, xGetProtlDown(self, 0), &part) == XK_FAILURE ) {
	xTrace0(chanp, TR_ERRORS,
		"chan_init: can't openenable transport protocol");
	xFree((char *) pstate);
    }
    xTrace0(chanp, TR_GROSS_EVENTS, "CHAN init done");
}


/* 
 * Adds an entry to the '{ peer -> { hlp -> newChannel } }' map for
 * the given peer if it doesn't already exist.  If it doesn't exist,
 * CHAN's interest in this host is communicated to BIDCTL.
 */
Map
chanGetChanMap( self, h )
    Protl	self;
    IPhost	*h;
{
    Map		hlpMap;
    Binding	b;
    PState	*ps = (PState *)self->state;

    if (mapResolve(ps->newChanMap, h, (void **)&hlpMap) == XK_FAILURE) {
	xTrace1(chanp, TR_EVENTS, "creating new 'newChanMap' for host %s",
		ipHostStr(h));
	if ( chanBidctlRegister(self, h) == XK_FAILURE )
	    return 0;
	hlpMap = mapCreate(CHAN_HLP_MAP_SZ, sizeof(IPhost));
	b = mapBind(ps->newChanMap, h, hlpMap);
	xAssert(b != ERR_BIND);
    }
    return hlpMap;
}


/* 
 * chanCreateSessn -- create a new channel session using the given
 * protocols and the information in 'key'.  The session is bound into
 * 'keyMap' with 'key' and is also bound into the mapchain rooted at
 * 'hostMap.'
 */
Sessn 
chanCreateSessn( self, hlp, hlpType, key, initFunc, keyMap, hostMap )
    Protl 	self, hlp, hlpType;
    ActiveID	*key;
    SessnInitFunc initFunc;
    Map		keyMap, hostMap;
{
    Sessn   	s;
    CHAN_STATE 	*ss;
    CHAN_HDR 	*hdr;
    
    xTrace0(chanp, TR_MAJOR_EVENTS, "CHAN createSessn ......................");
    xIfTrace(chanp, TR_MAJOR_EVENTS) chanDispKey(key);
    ss = X_NEW(CHAN_STATE);
    bzero((char *)ss, sizeof(CHAN_STATE));
    /*
     * Fill in  state
     */
    msg_clear(ss->saved_msg);
    semInit(&ss->reply_sem, 0);
    if ( xControlSessn(key->lls, GETPEERHOST, (char *)&ss->peer, sizeof(IPhost))
				< (int)sizeof(IPhost) ) {
	xTrace0(chanp, TR_ERRORS,
		"chan_open: can't do GETPEERHOST on lower session");
	return ERR_SESSN;
    }
    if ( chanGetChanMap(self, &ss->peer) == 0 ) {
	return ERR_SESSN;
    }
    /*
     * Fill in header
     */
    hdr = &ss->hdr;
    hdr->chan = key->chan;
    hdr->prot_id = key->prot_id;
    hdr->flags = USER_MSG;
    /*
     * Create session and bind to address
     */
    xDuplicate(key->lls);
    s = xCreateSessn(initFunc, hlp, hlpType, self, 1, &key->lls);
    s->state = (char *) ss;
    s->binding 	= mapBind(keyMap, key, s);
    /*
     * Just to be paranoid
     */
    if ( s->binding == ERR_BIND ) {
	xTrace0(chanp, TR_ERRORS, "chanCreateSessn: could not bind session"); 
	xTrace3(chanp, TR_ERRORS,
		"chanCreateSessn: lls = %x, chan = %d, prot_id = %d",
		key->lls, (int)key->chan, key->prot_id);
	xClose(s);
	return ERR_SESSN;
    }
    chanMapChainAddObject((VOID *)s, hostMap,
			  &ss->peer, key->prot_id, key->chan);
    xTrace1(chanp, TR_MAJOR_EVENTS, "chanCreateSessn returns %x", s);
    return s;
}



void
chanDestroy( s )
    Sessn 	s;
{
    CHAN_STATE	*sstate;
    PSTATE 	*pstate;
    Sessn	lls;
    
    xTrace0(chanp, TR_MAJOR_EVENTS, "CHAN Destroy ........................");
    xTrace1(chanp, TR_MAJOR_EVENTS, "Of session %x", s);
    xAssert(xIsSessn(s));
    xAssert(s->rcnt == 0);
    xAssert(s->binding == 0);
    pstate  = (PSTATE *)s->myprotl->state;
    sstate  = (CHAN_STATE *)s->state;
    chanEventFlush(s);
    /*
     * Free chan state
     */
    if (sstate) {
	msg_flush(sstate->saved_msg); 
	lls = xGetSessnDown(s, 0);
	if ( lls != ERR_SESSN ) {
	    xClose(lls);
	}
    }
    xDestroySessn(s);
    return;
}


/*
 * chanDemux
 */
static XkReturn
chanDemux(self, lls, msg)
    Protl self;
    Sessn lls;
    Msg *msg;
{
    CHAN_HDR 	hdr;
    Sessn   	s;
    ActiveID 	actKey;
    PassiveID 	pasKey;
    PSTATE 	*ps = (PSTATE *)self->state;
    Enable	*e;
    Map		map = 0;
    void        *buf;
    
    xTrace0(chanp, TR_EVENTS, "CHAN demux .............................");

    buf = msgPop(msg, CHANHLEN);
    if ( ! buf) {
	xError("chanDemux: msgPop failed");
	return XK_FAILURE;
    }
    chanHdrLoad(&hdr, buf, CHANHLEN, 0);

    xIfTrace(chanp, TR_MORE_EVENTS) { 
	pChanHdr(&hdr);
    } 
    /*
     * Check for active channel
     */
    bzero((char*)&actKey, sizeof(actKey));
    actKey.chan		= hdr.chan;
    actKey.prot_id 	= hdr.prot_id;
    actKey.lls 	= lls;
    xIfTrace(chanp, TR_DETAILED) {
	chanDispKey( &actKey );
    }
    map = (hdr.flags & FROM_CLIENT) ? ps->actSvrKeyMap : ps->actCliKeyMap;
    if (mapResolve(map, &actKey, (void **)&s) == XK_SUCCESS) {
	/*
	 * Pop to active channel
	 */
	xTrace1(chanp, TR_MORE_EVENTS, "chanDemux: existing channel %s", s);
	return xPop(s, lls, msg, &hdr);
    } 
    /* 
     * Look for an idle/dormant session
     */
    if ( ! (hdr.flags & FROM_CLIENT) ) {
	int	seq;

	seq = getIdleSeqNum(ps->idleCliMap, &actKey);
	if ( seq == -1 ) {
	    /* 
	     * We never heard of this channel -- we'll drop the msg
	     */
	    xTrace0(chanp, TR_SOFT_ERRORS,
		    "spurious msg for non-existent channel");
	} else {
	    chanClientIdleRespond(&hdr, actKey.lls, seq);
	}
    } else {
	/*
	 * Find openenable
	 */
	pasKey = actKey.prot_id;
	if (mapResolve(ps->passiveMap, &pasKey, (void **)&e) == XK_FAILURE) {
	    xTrace1(chanp, TR_EVENTS,
		    "chanDemux -- no openenable for hlp %d", hdr.prot_id);
	} else {
	    /* 
	     * Try to create a session
	     */
	    semWait(&ps->newSessnLock);
	    /* 
	     * Look again ...
	     */
	    if (mapResolve(map, &actKey, (void **)&s) == XK_FAILURE) {
		s = getSpecIdleSessn(self, e, &actKey, 
				     ps->idleSvrMap, ps->actSvrKeyMap);
		if ( s == ERR_SESSN ) {
		    s = chanSvcOpen(self, e->hlp, e->hlpType, &actKey, 0);
		}
	    } else {
		/*
		 * Pop to active channel
		 */
		xTrace1(chanp, TR_MORE_EVENTS, "chanDemux: new channel %s created by someone else", s);
	    } 
	    semSignal(&ps->newSessnLock);
	    if ( s != ERR_SESSN ) {
		xPop(s, lls, msg, &hdr);
	    } else {
		xTrace0(chanp, TR_SOFT_ERRORS,
			"chanDemux: can't create session ");
	    }
	}
    }
    return XK_SUCCESS;
}


/* 
 * chanCheckMsgLen -- checks the length field of the header with the
 * actual length of the message, truncating it if necessary.
 *
 * returns 0 if the message is long enough and
 * 	  -1 if the message is too short.
 */
int
chanCheckMsgLen( hdrLen, m )
    u_int 	hdrLen;
    Msg 	*m;
{
    u_int	dataLen;

    dataLen = msgLength(m);
    xTrace2(chanp, TR_DETAILED,
	    "chan checkLen: hdr->len = %d, msg_len = %d", hdrLen,
	    dataLen);
    if (hdrLen < dataLen) {
	xTrace0(chanp, TR_MORE_EVENTS, "chan_pop: truncating msg");
	msgTruncate(m, hdrLen);
    } else if (hdrLen > dataLen) {
	xTrace0(chanp, TR_SOFT_ERRORS, "chan_pop: message too short!");
	return -1;
    }
    return 0;
}


static int
chanControlProtl( self, op, buf, len )
    Protl 	self;
    int 	op, len;
    char 	*buf;
{
    PSTATE		*ps = (PSTATE *)self->state;
    BidctlBootMsg	*msg = NULL;

    switch ( op ) {

      case BIDCTL_FIRST_CONTACT:
	return 0;

      /* 
       * This handler must not block
       */
      case BIDCTL_PEER_REBOOTED:
	{
	    DisableStubInfo	*dsInfo;
	    Map			hlpMap;
	    XkReturn	res;

	    msg = (BidctlBootMsg *)buf;
	    xTrace1(chanp, TR_MAJOR_EVENTS,
		    "chan receives notification that peer %s rebooted",
		    ipHostStr(&msg->h));
	    if (mapResolve(ps->newChanMap, &msg->h, (void **)&hlpMap) ==
		XK_FAILURE) {
		/* 
		 * We shouldn't have been notified of this reboot
		 */
		xTrace1(chanp, TR_SOFT_ERRORS,
			"CHAN receives spurious notification of %s reboot",
			ipHostStr(&msg->h));
	    } else {
		chanClientPeerRebooted(ps, &msg->h);
		chanServerPeerRebooted(ps, &msg->h);
		mapClose(hlpMap);
		res = mapRemoveKey(ps->newChanMap, &msg->h);
		xAssert(res == XK_SUCCESS);
		dsInfo = X_NEW(DisableStubInfo);
		dsInfo->peer = msg->h;
		dsInfo->self = self;
		evDetach( evSchedule( disableStub, (VOID *)dsInfo, 0));
	    }
	}
	return 0;

      default:
	return xControlProtl(xGetProtlDown(self, 0), op, buf, len);
    }
}
  

/*
 * chanControlSessn
 */
int
chanControlSessn( self, opcode, buf, len )
    Sessn 	self;
    int 	opcode, len;
    char 	*buf;
{
    CHAN_STATE *sstate;
    
    xTrace0(chanp, TR_EVENTS, "CHAN controlsessn ......................");
    xTrace1(chanp, TR_EVENTS, "Of session=%x", self); 
    
    sstate = (CHAN_STATE *)self->state;
    
    switch (opcode) {
	
      case GETMYPROTO:
      case GETPEERPROTO:
	checkLen(len, sizeof(long));
	*(long *)buf = sstate->hdr.prot_id;
	return sizeof(long);

      case GETMAXPACKET:
      case GETOPTPACKET:
	checkLen(len, sizeof(int));
	if ( xControlSessn(xGetSessnDown(self, 0), opcode, buf, len) <= 0 ) {
	    return -1;
	}
	*(int *)buf -= sizeof(CHAN_HDR);
	return sizeof(int);
	
      case GETPEERHOST:
	checkLen(len, sizeof(IPhost));
	*(IPhost *)buf = sstate->peer;
	return sizeof(IPhost);

      case CHAN_SET_TIMEOUT:
	checkLen(len, sizeof(int));
	sstate->waitParam = *(int *)buf;
	xTrace1(chanp, TR_EVENTS, "channel session timeout set to %d",
		sstate->waitParam);
	return 0;
	
      case CHAN_SET_MAX_TIMEOUT:
	checkLen(len, sizeof(int));
	sstate->maxWait = *(int *)buf;
	xTrace1(chanp, TR_EVENTS, "channel session max timeout set to %d",
		sstate->maxWait);
	return 0;
	
      case CHAN_GET_TIMEOUT:
	checkLen(len, sizeof(int));
	*(int *)buf = sstate->waitParam;
	return sizeof(int);

      case CHAN_GET_MAX_TIMEOUT:
	checkLen(len, sizeof(int));
	*(int *)buf = sstate->maxWait;
	return sizeof(int);

      default:
	return xControlSessn(xGetSessnDown(self, 0), opcode, buf, len);
    }
}


/*
 * killticket: get rid of any dangling fragments 
 */
static void
killticket(s)
    Sessn s;
{
    CHAN_STATE *ss=(CHAN_STATE *)s->state;

#ifndef NO_KILLTICKET
    if ( ss->info.transport) {
	xControlProtl(ss->info.transport, FREERESOURCES, (char *)&ss->info.ticket, sizeof(ss->info.ticket));
    }
#endif
}


/* 
 * Release timeout resources currently held (timeout events, saved
 * messages, tickets for lower messages).  Works for both client and
 * server
 */
void
chanFreeResources(self)
    Sessn self;
{
    CHAN_STATE	*state;

    state = (CHAN_STATE *)self->state;
    xAssert(state);
    killticket(self);
    msg_flush(state->saved_msg);
    chanEventFlush(self);
}


/*
 * chanReply -- send a control reply (no user data) to lower session
 * 's' using the given header and flags.
 */
XkHandle
chanReply( s, hdr, flags )
    Sessn 	s;
    CHAN_HDR 	*hdr;
    int 	flags;
{
    CHAN_HDR 	hdr_copy;
    Msg 	msg;
    XkHandle rval;
    void        *buf;
    
    xAssert(xIsSessn(s));
    hdr_copy 	 = *hdr;
    hdr_copy.flags = flags;
    hdr_copy.len 	 = 0;
    xTrace0(chanp, TR_EVENTS, "chan_pop: Sending reply");
    xIfTrace(chanp, TR_MORE_EVENTS) {
	pChanHdr(&hdr_copy);
    }
    msgConstructEmpty(&msg);

    buf = msgPush(&msg, CHANHLEN);
    xAssert(buf);
    chanHdrStore(&hdr_copy, buf, CHANHLEN, 0);

    rval = xPush(s, &msg);
    msgDestroy(&msg);
    return rval;
}


/*
 * chanCheckSeq -- determine the relation of the new sequence number 
 * 'new_seq' to 'cur_seq'.  
 */
SEQ_STAT 
chanCheckSeq(cur_seq, new_seq)
    unsigned int cur_seq;
    unsigned int new_seq;
{
    if (cur_seq == new_seq) {
	xTrace0(chanp, TR_MORE_EVENTS, "chanPop: current sequence number");
	return(current);
    }
    if (cur_seq < new_seq) {
	xTrace0(chanp, TR_MORE_EVENTS, "chanPop: new sequence number");
	return(new);
    }
    xTrace0(chanp, TR_MORE_EVENTS, "chanPop: old sequence number");
    return(old);
}


/* 
 * chanGetProtNum -- determine the protocol number of 'hlp'
 * relative to this protocol
 */
long
chanGetProtNum( self, hlp )
    Protl	self, hlp;
{
    long n;

    if ( (n = relProtNum(hlp, self)) == -1 ) {
	xTrace1(chanp, TR_ERRORS,
		"chan could not get relative protocol number of %s",
		hlp->name);
    }
    return n;
}
  


/* 
 * getProcProtl
 */
static void 
getProcProtl(s)
    Protl s;
{
    xAssert(xIsProtl(s));
    s->controlprotl 	= chanControlProtl;
    s->open   	= chanOpen;
    s->openenable = chanOpenEnable;
    s->opendisable = chanOpenDisable;
    s->opendisableall = chanOpenDisableAll;
    s->demux 	= chanDemux;
}



/* 
 * Register this protocol's interest in 'peer' with BIDCTL
 */
XkReturn
chanBidctlRegister( self, peer )
    Protl	self;
    IPhost	*peer;
{
    Protl	llp;
    Part	p;
    
    xAssert(xIsProtl(self));
    xTrace1(chanp, TR_MAJOR_EVENTS,
	    "chan registering interest in peer %s with bidctl",
	    ipHostStr(peer));
    llp = xGetProtlDown(self, CHAN_BIDCTL_I);
    xAssert(xIsProtl(llp));
    partInit(&p, 1);
    partPush(p, (VOID *)peer, sizeof(IPhost));
    return xOpenEnable(self, self, llp, &p);
}


/* 
 * Register this protocol's interest in 'peer' with BIDCTL
 */
static XkReturn
chanBidctlUnregister( self, peer )
    Protl	self;
    IPhost	*peer;
{
    Protl	llp;
    Part	p;
    
    xAssert(xIsProtl(self));
    xTrace1(chanp, TR_MAJOR_EVENTS,
	    "chan unregistering interest in peer %s with bidctl",
	    ipHostStr(peer));
    llp = xGetProtlDown(self, CHAN_BIDCTL_I);
    xAssert(xIsProtl(llp));
    partInit(&p, 1);
    partPush(p, (VOID *)peer, sizeof(IPhost));
    return xOpenDisable(self, self, llp, &p);
}


static void
disableStub( ev, arg )
    Event	ev;
    VOID 	*arg;
{
    DisableStubInfo	*dsInfo = (DisableStubInfo *)arg;

    xAssert(dsInfo);
    xTrace1(chanp, TR_MAJOR_EVENTS, "chan disable stub runs for host %s",
	    ipHostStr(&dsInfo->peer));
    chanBidctlUnregister(dsInfo->self, &dsInfo->peer);
    xFree((char *)dsInfo);
}


/* 
 * Remove the session 's' from the active maps 'keyMap' and 'hostMap'.
 * The session must actually be in the maps.
 */
void
chanRemoveActive( s, keyMap, hostMap )
    Sessn	s;
    Map		keyMap, hostMap;
{
    XkReturn	res;
    Map			chanMap;
    SState		*ss = (SState *)s->state;
#ifdef XK_DEBUG
    Sessn		oldSessn;
#endif

    /* 
     * Remove from active key map
     */
    xAssert(s->binding);
    res = mapRemoveBinding(keyMap, s->binding);
    xAssert( res == XK_SUCCESS );
    s->binding = 0;
    chanMap = chanMapChainFollow(hostMap, &ss->peer, ss->hdr.prot_id);
    xAssert( chanMap );
    xAssert(mapResolve(chanMap, &ss->hdr.chan, (void **)&oldSessn) ==
	    XK_SUCCESS && oldSessn == s );
    res = mapRemoveKey(chanMap, &ss->hdr.chan);
    xAssert( res == XK_SUCCESS );
}


/* 
 * Removes the session from the 'fromMap' and binds it in the idleMap
 * series starting at 'toMap', using the peer, prot_id and channel
 * found in the session state.
 */
XkReturn
chanAddIdleSessn( hostMap, fromMap, toMap, s )
    Map		hostMap, fromMap, toMap;
    Sessn	s;
{
    IPhost	peerHost;
    CHAN_STATE	*ss = (CHAN_STATE *)s->state;
    long        seq;

    if ( xControlSessn(xGetSessnDown(s, 0), GETPEERHOST, (char *)&peerHost,
		  sizeof(IPhost)) < (int)sizeof(IPhost) ) {
	xTrace0(chanp, TR_ERRORS, "chanClientClose could not get peer host");
	return XK_FAILURE;
    }
    chanRemoveActive(s, fromMap, hostMap);
    /* 
     * We don't need to store the session if it's sequence number is
     * the default.
     */
    seq = ss->hdr.seq;
    if ( ss->hdr.seq > START_SEQ ) {
	chanMapChainAddObject((void *)seq, toMap, &peerHost,
			      ss->hdr.prot_id, ss->hdr.chan);
    }
    chanDestroy(s);
    return XK_SUCCESS;
}


/* 
 * Looks for a dormant session corresponding to the active key,
 * looking in the map chain starting at idleMap.  If
 * such a session is found, the corresponding sequence number is
 * returned.  The sequence number is not unbound from the map.
 * If no dormant session is found, -1 is returned.
 */
static int
getIdleSeqNum( idleMap, key )
    ActiveID	*key;
    Map		idleMap;
{
    Map		map = 0;
    Channel	chan;
    int		seq;

    if ( ! (map = chanGetMap(idleMap, key->lls, key->prot_id)) ) {
	xTrace0(chanp, TR_EVENTS, "chanGetIdleSeqNum -- no map");
	return -1;
    }
    chan = key->chan;
    if (mapResolve(map, &chan, (void **)&seq) == XK_FAILURE)
	seq = -1;
    return seq;
}


/* 
 * Restores the appropriate dormant idle session if it exists.
 * The session is removed from the idle map and placed in the active map.
 */
static Sessn
getSpecIdleSessn( self, e, key, idleMap, actMap )
    Protl	self;
    ActiveID	*key;
    Enable	*e;
    Map		idleMap, actMap;
{
    Map			map = 0;
    Channel		chan;
    XkReturn	res;
    int			seq;

    if ( ! (map = chanGetMap(idleMap, key->lls, key->prot_id)) ) {
	xTrace0(chanp, TR_EVENTS, "chanGetSpecIdleSessn -- no map");
	return ERR_SESSN;
    }
    chan = key->chan;
    if (mapResolve(map, &chan, (void **)&seq) == XK_SUCCESS) {
	Sessn	s;

	xTrace1(chanp, TR_EVENTS,
		"chanGetSpecIdleSessn -- found stored sequence #%d", seq);
	s = chanSvcOpen(self, e->hlp, e->hlpType, key, seq);
	if ( s == ERR_SESSN ) {
	    xTrace1(chanp, TR_ERRORS,
		    "chan getSpecIdleSessn could not reopen chan %d",
		    key->chan);
	    return ERR_SESSN;
	}
	/* 
	 * Remove this session from the idle map
	 */
	res = mapRemoveKey(map, &chan);
	xAssert(res == XK_SUCCESS);
	return s;
    } else {
	xTrace0(chanp, TR_EVENTS,
		"chanGetSpecIdleSessn -- found no appropriate session");
	return ERR_SESSN;
    }
}


int
chanMapRemove( key, val )
    VOID	*key;
    VOID	*val;
{
    return MFE_CONTINUE | MFE_REMOVE;
}


/* 
 * Follows the map chain starting at 'm', looking for the map
 * corresponding to the given lls and hlp number.
 */
Map
chanGetMap( m, lls, prot )
    Map		m;
    Sessn	lls;
    long	prot;
{
    IPhost	peerHost;

    if ( xControlSessn(lls, GETPEERHOST, (char *)&peerHost,
		  sizeof(IPhost)) < (int)sizeof(IPhost) ) {
	xTrace0(chanp, TR_ERRORS, "chanGetIdleMap could not get peer host");
	return 0;
    }
    return chanMapChainFollow(m, &peerHost, prot);
}


/* 
 * Send a message to the lower session of 's' using 'flags.'  The
 * saved user message will be sent if the 'forceUsrMsg' parameter is
 * non-zero or if the user message will fit in a single packet.
 *
 * The saved user message must be valid.
 *
 * Returns a handle on the outgoing message.
 */
XkHandle
chanResend( s, flags, forceUsrMsg )
    Sessn	s;
    int		flags, forceUsrMsg;
{
    CHAN_HDR		hdr;
    SState		*ss = (SState *)s->state;
    Msg			packet;
    XkHandle	rval;
    void                *buf;

    hdr = ss->hdr;
    hdr.flags = flags;
    xAssert( ! msg_isnull(ss->saved_msg));
    xAssert( (flags & ACK_REQUESTED) || forceUsrMsg );

    if ( ss->info.reliable ) {
	return chanReply(xGetSessnDown(s, 0), &hdr, flags);
    }
    if ( ss->info.expensive ) {
	if (! forceUsrMsg) {
	    return chanReply(xGetSessnDown(s, 0), &hdr, flags);
        }
	if (ss->info.transport) {
	    if (!xControlProtl(ss->info.transport,CHAN_RETRANSMIT,
			  (char *)&ss->info.ticket, sizeof(XkHandle))) {
	        return chanReply(xGetSessnDown(s, 0), &hdr, flags);
	    }
	}
    }
	        
    if ( ! forceUsrMsg ) {
	int	size;
    
	if ( xControlSessn(s, GETOPTPACKET, (char *)&size, sizeof(int))
	    						< (int)sizeof(int) ) {
	    xTraceS0(s, TR_ERRORS, "resend couldn't GETOPTPKT");
	    size = 0;
	}
	if ( msgLength(&ss->saved_msg.m) > size ) {
	    return chanReply(xGetSessnDown(s, 0), &hdr, flags);
	}
    }

    msgConstructCopy(&packet, &ss->saved_msg.m);
    hdr.flags |= USER_MSG;

    xIfTrace(chanp, TR_MORE_EVENTS) { 
	pChanHdr(&hdr);
    } 

    buf = msgPush(&packet, CHANHLEN);
    xAssert(buf);
    chanHdrStore(&hdr, buf, CHANHLEN, 0);

    /*
     * Send message
     */
    bzero((char *)&ss->info, sizeof(ss->info));
    msgSetAttr(&packet, 0, (void *)&ss->info, sizeof(ss->info));
    xAssert(xIsSessn(xGetSessnDown(s, 0)));
    rval = xPush(xGetSessnDown(s, 0), &packet);
    msgDestroy(&packet);
    return rval;
}


/* 
 * Run on inactive channels, asks for ACK
 */
void
chanTimeout( ev, arg )
    Event	ev;
    VOID 	*arg;
{
    Sessn		s = (Sessn)arg;
    CHAN_STATE		*ss;
    XkHandle	rval;
    int			flags;
    
    xAssert(xIsSessn(s));
    ss = (CHAN_STATE *)s->state; 
    xTrace1(chanp, TIMEOUT_TRACE, "CHAN: %sTimeout: timeout!",
	    ss->hdr.flags & FROM_CLIENT ? "client" : "server");
    if ( ss->hdr.flags & FROM_CLIENT ) {
	flags = ACK_REQUESTED | FROM_CLIENT;
    } else {
	flags = ACK_REQUESTED;
    }
    rval = chanResend(s, flags, 0);
    if ( evIsCancelled(ev) ) {
	xTrace0(chanp, TR_EVENTS, "chanTimeout cancelled");
	return;
    }
    /* 
     * Detach myself
     */
    evDetach(ss->event);
    ss->wait = MIN(2*ss->wait, ss->maxWait);
    xTrace1(chanp, TIMEOUT_TRACE, "new timeout value: %d", ss->wait);
    ss->event = evSchedule(chanTimeout, s, ss->wait);
}
