/* 
 * vdelay.c
 *
 * x-kernel v3.3
 *
 * Copyright (c) 1996,1993,1991,1990  Arizona Board of Regents
 *
 * $Revision: 1.1 $
 * $Date: 1996/06/14 23:49:05 $
 */

#include "xkernel.h"
#include "vdelay.h"
#include "vdelay_i.h"

#ifdef __STDC__

static void     protlFuncInit(Protl);
static void     sessnInit(Sessn);
static Sessn    vdelayCreateSessn(Protl, Protl, Protl, Sessn);
static Sessn    vdelayOpen(Protl, Protl, Protl, Part *);
static XkReturn vdelayOpenDisable(Protl, Protl, Protl, Part *);
static XkReturn vdelayOpenDone(Protl, Protl, Sessn, Protl);
static XkReturn vdelayOpenEnable(Protl, Protl, Protl, Part *);
static int      vdelayControlProtl(Protl, int, char *, int);
static int      vdelayControlSessn(Sessn, int, char *, int);
static XkReturn vdelayClose(Sessn);
static XkReturn vdelayPop(Sessn, Sessn, Msg *, void *);
static XkHandle vdelayPush(Sessn, Msg *);
static XkReturn vdelayProtlDemux(Protl, Sessn, Msg *);
static XkReturn vdelaySessnDemux(Protl, Sessn, Msg *);

#else

static void     protlFuncInit();
static void     sessnInit();
static Sessn    vdelayCreateSessn();
static Sessn    vdelayOpen();
static XkReturn vdelayOpenDisable();
static XkReturn vdelayOpenDone();
static XkReturn vdelayOpenEnable();

#endif /* __STDC__ */

int tracevdelayp;

static XkReturn
readInt(self, str, nFields, line, arg)
Protl self;
int   line, nFields;
char  **str;
VOID  *arg;
{
    return sscanf(str[2], "%d", arg) < 1  ? XK_FAILURE : XK_SUCCESS;
}

static ProtlRomOpt vdelayOpt[] = {
    { "delay", 3, readInt }
};

void
vdelay_init(self)
Protl self;
{
    PState *ps;
    
    xTraceP0(self, TR_GROSS_EVENTS, "init");
    if (!xIsProtl(xGetProtlDown(self, 0))) {
	xError("VDELAY down vector is misconfigured");
	return;
    }
    ps = X_NEW(PState);
    self->state = (VOID *)ps;
    protlFuncInit(self);
    ps->activeMap = mapCreate(VDELAY_ACT_MAP_SZ, sizeof(Sessn));
    ps->passiveMap = mapCreate(VDELAY_PAS_MAP_SZ, sizeof(Protl));
}

static Sessn
vdelayOpen(self, hlp, hlpType, p)
Protl self, hlp, hlpType;
Part  *p;
{
    Sessn  s, lls;
    PState *ps = (PState *)self->state;
    
    xTraceP0(self, TR_MAJOR_EVENTS, "open");
    if ((lls = xOpen(self, hlpType, xGetProtlDown(self, 0), p)) == ERR_SESSN) {
	xTraceP0(self, TR_ERRORS, "open: could not open lower session");
	return ERR_SESSN;
    }
    if (mapResolve(ps->activeMap, &lls, (void **)&s) == XK_SUCCESS)
	xTraceP0(self, TR_MORE_EVENTS, "open -- found an existing one");
    else {
	xTraceP0(self, TR_MAJOR_EVENTS, "open -- creating a new one");
	if ((s = vdelayCreateSessn(self, hlp, hlpType, lls)) == ERR_SESSN)
	    xClose(lls);
    }
    return s;
}

static Sessn
vdelayCreateSessn(self, hlp, hlpType, lls)
Protl self, hlp, hlpType;
Sessn lls;
{
    Sessn  s;
    PState *ps = (PState *)self->state;
    SState *ss;
    
    if ((s = xCreateSessn(sessnInit, hlp, hlpType, self, 1, &lls)) ==
	ERR_SESSN) {
	xTraceP0(self, TR_ERRORS, "xCreateSessn fails");
	return ERR_SESSN;
    }
    s->binding = mapBind(ps->activeMap, &lls, s);
    if (s->binding == ERR_BIND) {
	xTraceP0(self, TR_ERRORS, "mapBind fails in createSessn");
	return ERR_SESSN;
    }
    /*
     * The lower sessions' up field is made to point to this
     * vdelay session (not the protocol)
     */
    xSetUp(lls, (Protl)s);
    ss = X_NEW(SState);
    s->state = (VOID *)ss;
    ss->delay = -1;
    findProtlRomOpts(self, vdelayOpt, sizeof(vdelayOpt)/sizeof(vdelayOpt[0]),
                    &ss->delay);
    if (ss->delay <= 0) {
        ss->delay = VDELAY_DEFAULT;
    } /* if */
    xTraceP1(self, TR_MAJOR_EVENTS, "delaying packets %d ms",
	    ss->delay);
    xTraceP1(self, TR_MAJOR_EVENTS, "open returns %x", s);
    return s;
}

static int
vdelayControlSessn(s, opcode, buf, len)
Sessn s;
int   opcode, len;
char  *buf;
{
    SState *ss = (SState *)s->state;

    xTraceS0(s, TR_EVENTS, "controlsessn");
    switch (opcode) {
        case VDELAY_SETDELAY:
	    ss->delay = *(int *)buf;
	    xTraceS1(s, TR_EVENTS, "controlsessn resets delay to %d",
		     ss->delay);
	    return 0;
        case VDELAY_GETDELAY:
	    *(int *)buf = ss->delay;
	    return sizeof(int);
        default:
	    /*
	     * All other opcodes are forwarded to the lower session.  
	     */
	    return xControlSessn(xGetSessnDown(s, 0), opcode, buf, len);
    }
}

static int
vdelayControlProtl(self, opcode, buf, len)
Protl self;
int   opcode, len;
char  *buf;
{
    /*
     * All opcodes are forwarded to the lower protocol
     */
    return xControlProtl(xGetProtlDown(self, 0), opcode, buf, len);
}

static XkReturn
vdelayOpenEnable(self, hlp, hlpType, p)
Protl self, hlp, hlpType;
Part  *p;
{
    PState *ps = (PState *)self->state;
    
    xTraceP0(self, TR_MAJOR_EVENTS, "open enable");
    return defaultVirtualOpenEnable(self, ps->passiveMap, hlp, hlpType,
				    self->down, p);
}

static XkReturn
vdelayOpenDisable(self, hlp, hlpType, p)
Protl self, hlp, hlpType;
Part  *p;
{
    PState *ps = (PState *)self->state;

    return defaultVirtualOpenDisable(self, ps->passiveMap, hlp, hlpType,
				     self->down, p);
}

static XkReturn
vdelayClose(s)
Sessn s;
{
    PState   *ps;
    XkReturn res;
    Sessn    lls;
    
    xTraceS1(s, TR_MAJOR_EVENTS, "close of session %x", s);
    ps = (PState *)xMyProtl(s)->state;
    res = mapRemoveBinding(ps->activeMap, s->binding);
    lls = xGetSessnDown(s, 0);
    xSetUp(lls, xMyProtl(s));
    xClose(lls);
    xDestroySessn(s);
    return XK_SUCCESS;
}

static XkHandle
vdelayPush(s, msg)
Sessn s;
Msg   *msg;
{
    xTraceS0(s, TR_EVENTS, "push");
    return xPush(xGetSessnDown(s, 0), msg);
}

static XkReturn
vdelayOpenDone(self, llp, lls, hlpType)
Protl self, llp, hlpType;
Sessn lls;
{
    Sessn  s;
    PState *ps = (PState *)self->state;
    Enable *e;
    
    xTraceP0(self, TR_MAJOR_EVENTS, "In openDone");
    if (self == hlpType) {
	xTraceP0(self, TR_ERRORS, "self == hlpType in openDone");
	return XK_FAILURE;
    }
    /*
     * check for openEnable
     */
    
    if (mapResolve(ps->passiveMap, &hlpType, (void **)&e) == XK_FAILURE) {
	/* 
	 * This shouldn't happen
	 */
	xTraceP0(self, TR_ERRORS,
		"openDone: Couldn't find hlp for incoming session");
	return XK_FAILURE;
    }
    if ((s = vdelayCreateSessn(self, e->hlp, e->hlpType, lls)) == ERR_SESSN) {
	return XK_FAILURE;
    }
    xDuplicate(lls);
    xTraceP0(self, TR_EVENTS,
	    "passively opened session successfully created");
    return xOpenDone(e->hlp, self, s);
}

static XkReturn
vdelayProtlDemux(self, lls, m)
Protl self;
Sessn lls;
Msg   *m;
{
    xTraceP0(self, TR_ERRORS, "protlDemux called!!");
    return XK_SUCCESS;
}

/* 
 * vdelayPop and vdelaySessnDemux must be used (i.e., they can't be
 * bypassed) for the UPI reference count mechanism to work properly. 
 */
static XkReturn
vdelayPop(s, lls, msg, h)
Sessn s, lls;
Msg   *msg;
VOID  *h;
{
    SState *ss = (SState *)s->state;

    xTraceS0(s, TR_EVENTS, "pop");
    Delay(ss->delay);
    return xDemux(xGetUp(s), s, msg);
}

static XkReturn
vdelaySessnDemux(self, lls, msg)
Protl self;
Sessn lls;
Msg   *msg;
{
    xTraceS0(self, TR_EVENTS, "session demux");
    return xPop((Sessn)self, lls, msg, 0);
}

static void
sessnInit(s)
Sessn s;
{
    s->push = vdelayPush;
    s->pop = vdelayPop;
    s->close = vdelayClose;
    s->controlsessn = vdelayControlSessn;
    /* 
     * VDELAY sessions will look like a protocol to lower sessions, so we
     * need a demux function
     */
    s->demux = vdelaySessnDemux;
}

static void
protlFuncInit(p)
Protl p;
{
    p->controlprotl = vdelayControlProtl;
    p->open = vdelayOpen;
    p->openenable = vdelayOpenEnable;
    p->opendisable = vdelayOpenDisable;
    p->demux = vdelayProtlDemux;
    p->opendone = vdelayOpenDone;
}
