/* 
 * $RCSfile: port_mgr.c,v $
 *
 * x-kernel v3.3
 *
 * Copyright (c) 1993,1991,1990,1996  Arizona Board of Regents
 *
 * $Log: port_mgr.c,v $
 * Revision 1.2  1996/01/29 22:39:20  slm
 * Updated copyright and version.
 *
 * Revision 1.1  1995/07/28  22:21:38  slm
 * Initial revision
 *
 * Revision 1.24.1.3  1994/12/02  18:30:34  hkaram
 * Changed to new mapResolve interface
 *
 * Revision 1.24.1.2  1994/11/22  21:04:46  hkaram
 * Added mapResolve casts
 *
 * Revision 1.24.1.1  1994/11/02  20:59:27  hkaram
 * New branch
 *
 * Revision 1.24  1994/09/21  02:10:41  davidm
 * Fixed tracing message to use xTrace() instead of fprintf(stderr, ...).
 *
 * Revision 1.23  1994/09/20  19:10:11  gkim
 * added support for port ranges (ANY_HIGH_PORT, ANY_LOW_PORT)
 *
 * Revision 1.22  1994/01/08  21:29:39  menze
 *   [ 1994/01/03          menze ]
 *   Uses the protocol-specific trace variable to avoid warnings when > 1
 *   protocols using the port_mgr are linked together
 */

/* 
 * Management of ports
 *
 * This file is designed to be included by another source file which
 * defines these macros:
 *
 *	MAX_PORT        -- maximum allowed port
 *	FIRST_USER_PORT -- the first port which may be handed out through
 *			   'getFreePort'
 *	NAME            -- token to prepend to the routine names
 *	PROT_NAME       -- string of protocol name (for debugging)
 *	TRACE_VAR       -- trace variable to use in tracing
 * 
 * NOTE -- this code assumes a port is no larger than an int.
 */

#include "xkernel.h"

#define new(Type) (Type *)xMalloc(sizeof(Type))

#ifdef __STDC__
extern int PASTE(trace,TRACE_VAR) ;
#else
extern int trace/**/TRACE_VAR ;
#endif

#define DUMP(_ps)    xIfTrace(TRACE_VAR, TR_DETAILED) { displayMap(_ps); }

typedef struct {
    int	 rcnt;
    long port;
} PortDesc;

typedef struct {
    Map           portMap;
    unsigned long nextPort;
} port_state;

#ifdef __STDC__

static int  displayElem(void *, void *, void *);
static void displayMap(port_state *);
static int  portBind(port_state *, long, int) ;
static void portUnbind(port_state *, PortDesc *);

#else

static int  displayElem();
static void displayMap();
static int  portBind() ;
static void portUnbind();

#endif /* __STDC__ */

void
#ifdef __STDC__
PASTE(NAME,PortMapInit)
#else
NAME/**/PortMapInit  
#endif
    (pst)
VOID *pst;
{
    port_state **ps = (port_state **)pst;

    if (!(*ps))  {
        *ps = (port_state *)xMalloc(sizeof (port_state));
        bzero((char *)*ps, sizeof(port_state));
    }
    if (!(*ps)->portMap) {
        (*ps)->portMap = mapCreate(PORT_MAP_SIZE, sizeof(long));
        (*ps)->nextPort = FIRST_USER_PORT;
    }
}

void
#ifdef __STDC__
PASTE(NAME,PortMapClose)
#else
NAME/**/PortMapClose  
#endif
    (pst)
VOID *pst;
{
    port_state **ps = (port_state **)pst;

    if (!*ps)  return;
    if ((*ps)->portMap)
        mapClose((*ps)->portMap);
    xFree((char *)*ps);
}

struct dmargs {
    int  i;
    char msgBuf[200];
};

static int
displayElem(key, value, idv)
VOID *key;
VOID *value;
VOID *idv;
{
    PortDesc      *pd = (PortDesc *)value;
    struct dmargs *idx = (struct dmargs *)idv;

    xAssert(pd);
    sprintf(idx->msgBuf, "Element %d:	  port = %d  rcnt = %d",
	    ++idx->i, pd->port, pd->rcnt);
    xError(idx->msgBuf);
    return MFE_CONTINUE;
}

static void
displayMap(ps)
port_state *ps;
{
    struct dmargs args;

    args.i = 0;
    sprintf(args.msgBuf, "dump of %s port map:", PROT_NAME);
    xError(args.msgBuf);
    mapForEach(ps->portMap, displayElem, &args);
}

/* 
 * Binds 'port' into the map with the indicated reference count.
 * Returns 0 on a successful bind, 1 if the port could not be bound
 * (indicating that it was already bound.)
 */
static int
portBind(ps, port, rcnt)
port_state *ps;
long       port;
int        rcnt;
{
    PortDesc *pd;

    pd = new(PortDesc);
    pd->rcnt = rcnt;
    pd->port = port;
    if (mapBind(ps->portMap, &port, pd) == ERR_BIND) {
	xFree((char *)pd);
	return 1;
    } 
    return 0;
}

static void
portUnbind(ps, pd)
port_state *ps;
PortDesc   *pd;
{
    xAssert(pd && pd != (PortDesc *) -1);
    mapRemoveKey(ps->portMap, &pd->port);
    xFree((char *)pd);
}

int
#ifdef __STDC__
PASTE(NAME,GetFreePort)
#else
NAME/**/GetFreePort
#endif
    (pst, port)
VOID *pst;
long *port;
{
    port_state    *ps = (port_state *)pst;
    unsigned long firstPort;

    xAssert(ps->portMap);
    firstPort = ps->nextPort;
    do {
	*port = ps->nextPort;
	if (ps->nextPort >= MAX_PORT)
	    ps->nextPort = FIRST_USER_PORT;
	else
	    ps->nextPort++;
	if (portBind(ps, *port, 1) == 0) {
	    /* Found a free port */
	    DUMP(ps);
	    return 0;
	}
    } while (ps->nextPort != firstPort);
    return 1;
}

/* 
 * we avoid doing the ps->nextPort logic by preserving its value.
 * the range of ports searched by GetFreePortRange starts at (ps->nextPort+1).
 */
int
#ifdef __STDC__
PASTE(NAME,GetFreePortRange)
#else
NAME/**/GetFreePortRange
#endif
    (pst, lowbound, highbound, port)
VOID *pst;
long lowbound, highbound;
long *port;
{
    port_state *ps = (port_state *)pst;
    unsigned long firstPort;

    xAssert(ps->portMap);
    firstPort = ps->nextPort;

    /* get the first available port between {lowbound, highbound} and
     * 		{FIRST_USER_PORT, MAX_PORT}.  
     * set ps->nextPort to the next available port, within {FIRST_USER_PORT,
     *		MAX_PORT}
     */

    *port = (lowbound > ps->nextPort + 1) ? lowbound : ps->nextPort + 1;
    while (*port != ps->nextPort) {
	if (*port >= MAX_PORT || *port >= highbound) {
	    /*
	     * if FIRST_USER_PORT is below our lower bound, then we
	     * start at the lower bound.
	     */
	    *port = (FIRST_USER_PORT < lowbound) ? lowbound : 
	    		FIRST_USER_PORT;
	}
	else
	    (*port)++;
	if (portBind(ps, *port, 1) == 0) {
	    /* Found a free port */
	    DUMP(ps);
	    xTrace3(TRACE_VAR, TR_EVENTS,
		    "GetFreePortRange: (low=%d, hi=%d, ret=%d)",
		    lowbound, highbound, *port);
	    return 0;
	}
    }
    return 1;
}

int
#ifdef __STDC__
PASTE(NAME,DuplicatePort)
#else
NAME/**/DuplicatePort
#endif
    (pst, port)
VOID *pst;
long port;
{
    PortDesc   *pd;
    int        res;
    port_state *ps = (port_state *)pst;

    xAssert(ps->portMap);
    if (port > MAX_PORT)
	res = 2;
    else {
	if (mapResolve(ps->portMap, &port, (void **)&pd) == XK_FAILURE) {
	    /* Port is not used, so we know portBind will succeed. */
	    res = portBind(ps, port, 1);
	}
	else {
	    pd->rcnt++;
	    res = 0;
	}
    }
    DUMP(ps);
    return res;
}

void
#ifdef __STDC__
PASTE(NAME,ReleasePort)
#else
NAME/**/ReleasePort
#endif
    (pst, port)
VOID *pst;
long port;
{
    PortDesc   *pd;
    port_state *ps = (port_state *)pst;

    xAssert(ps->portMap);
    if (mapResolve(ps->portMap, &port, (void **)&pd) == XK_SUCCESS) {
	if (pd->rcnt > 0) {
	    if (--pd->rcnt == 0)
		portUnbind(ps, pd);
	}
    }
    DUMP(ps);
}
