/*     
 * $RCSfile: asp.c,v $
 *
 * x-kernel v3.3
 *
 * Copyright (c) 1993,1991,1990,1996  Arizona Board of Regents
 */

#include "asp_internal.h"

void
asp_init(Protl self)
{
    ProtlState *pstate;
    Protl      llp;
    Part       part;

    getproc_protl(self);

    /* create and initialize protocol state */
    pstate = X_NEW(ProtlState);
    bzero((char *)pstate, sizeof(ProtlState));
    self->state = (void *)pstate;
    pstate->activemap  = mapCreate(ACTIVE_MAP_SIZE, sizeof(ActiveId));
    pstate->passivemap = mapCreate(PASSIVE_MAP_SIZE, sizeof(PassiveId));

    /* find lower level protocol and do a passive open on it */
    llp = xGetProtlDown(self, 0);
    if (!xIsProtl(llp))
        Kabort("ASP could not get lower protocol");
    partInit(&part, 1);
    partPush(part, ANY_HOST, 0);
    if (xOpenEnable(self, self, llp, &part) == XK_FAILURE) {
        xTrace0(aspp, TR_ALWAYS,
		"asp_init: openenable on lower protocol failed");
        xFree((char *) pstate);
        return;
    }
}

static void
getproc_protl(Protl p)
{
    /* fill in the function pointers to implement protocol operations */
    p->open         = aspOpen;
    p->openenable   = aspOpenEnable;
    p->demux        = aspDemux;
    p->controlprotl = aspControlProtl;
}


static Sessn
aspOpen(Protl self, Protl hlp, Protl hlpType, Part *p)
{
    ActiveId   key;
    Sessn      asp_s, lls;
    ProtlState *pstate = (ProtlState *)self->state;

    bzero((char *)&key, sizeof(key));

    /* high level protocol must specify both local and remote ASP port */
    key.remoteport = *((ASPport *)partPop(p[0]));
    key.localport  = *((ASPport *)partPop(p[1]));

    /* attempt to open session on protocol below this one */
    lls = xOpen(self, self, xGetProtlDown(self, 0), p);
    if (lls != ERR_SESSN) {
        key.lls = lls;
	/* check for this session in the active map */
        if (mapResolve(pstate->activemap, &key, (void **)&asp_s) == XK_FAILURE){
	    /* session wasn't already in map, so initialize it */
            asp_s = aspCreateSessn(self, hlp, hlpType, &key);
            if (asp_s != ERR_SESSN)    /* A successful open! */
                return asp_s;
        }
        /* if control makes it this far, an error has occurred */
        xClose(lls);
    }
    return ERR_SESSN;
}

static XkReturn
aspOpenEnable(Protl self, Protl hlp, Protl hlpType, Part *p)
{
    PassiveId  key;
    ProtlState *pstate = (ProtlState *)self->state;
    Enable     *e;

    key = *((ASPport *)partPop(*p));

    /* check if this port has already been openenabled */
    if (mapResolve(pstate->passivemap, &key, (void **)&e) != XK_FAILURE) {
        if (e->hlp == hlp) {
	    /* this port was openenabled previously by the same hlp */
            e->rcnt++;
            return XK_SUCCESS;
        }
	/* this port was openenabled previously by a different hlp - error */
        return XK_FAILURE;
    }

    /* this will be a new enabling, so create and initialize Enable object, */
    /* and enter the binding of port/enable object in the passive map */
    e = X_NEW(Enable);
    e->hlp     = hlp;
    e->hlpType = hlpType;
    e->rcnt    = 1;
    e->binding = mapBind(pstate->passivemap, &key, e);
    if (e->binding == ERR_BIND) {
        xFree((char *)e);
        return XK_FAILURE;
    }
    return XK_SUCCESS;
}

static Sessn
aspCreateSessn(Protl self, Protl hlp, Protl hlpType, ActiveId *key)
{
    Sessn      s;
    ProtlState *pstate = (ProtlState *)self->state;
    SessnState *sstate;
    ASPhdr     *asph;

    /* create the session object and initialize it */
    s = xCreateSessn(getproc_sessn, hlp, hlpType, self, 1, &key->lls);
    s->binding = mapBind(pstate->activemap, key, s);
    sstate = X_NEW(SessnState);
    s->state = (char *)sstate;

    /* create an ASP header */
    asph = &(sstate->hdr);
    asph->sport = key->localport;
    asph->dport = key->remoteport;
    asph->ulen  = 0;

    return s;
}

static void
getproc_sessn(Sessn s)
{
    /* fill in the function pointers to implement session operations */
    s->push            = aspPush;
    s->pop             = aspPop;
    s->controlsessn    = aspControlSessn;
    s->getparticipants = aspGetParticipants;
    s->close           = aspClose;
}

static XkReturn
aspDemux(Protl self, Sessn lls, Msg *dg)
{
    char       *buf;
    ASPhdr     h;
    ActiveId   activeid;
    ProtlState *pstate = (ProtlState *)self->state;
    Sessn      s;
    PassiveId  passiveid;
    Enable     *e;

    /* extract the header from the message */
    buf = msgPop(dg, HLEN);
    if (buf == NULL)
        return XK_FAILURE;
    aspHdrLoad(&h, buf, HLEN);

    /* construct a demux key from the header */
    bzero((char *)&activeid, sizeof(activeid));
    activeid.lls        = lls;
    activeid.localport  = h.dport;
    activeid.remoteport = h.sport;

    /* see if demux key is in the active map */
    if (mapResolve(pstate->activemap, &activeid, (void **)&s) == XK_FAILURE) {
	/* didn't find an active session, so check passive map */
        passiveid = h.dport;
        if (mapResolve(pstate->passivemap, &passiveid, (void **)&e) ==
	        XK_FAILURE) {
            /* drop the message */
            return XK_SUCCESS;
        }

        /* port was enabled, so create a new session and inform hlp */
        s = aspCreateSessn(self, e->hlp, e->hlpType, &activeid);
        if (s == ERR_SESSN)
            return XK_SUCCESS;
        xDuplicate(lls);
        xOpenDone(e->hlp, self, s);
    }

    /* found (or created) an appropriate session, so pop to it */
    return xPop(s, lls, dg, &h);
}

static long
aspHdrLoad(ASPhdr *hdr, char *src, long len)
{
    /* copy from src to hdr, then convert network byte order to hsot order */
    bcopy(src, (char *)hdr, HLEN);
    hdr->ulen  = ntohs(hdr->ulen);
    hdr->sport = ntohs(hdr->sport);
    hdr->dport = ntohs(hdr->dport);
    return HLEN;
}

static void
aspHdrStore(ASPhdr *hdr, char *dst, long len)
{
    /* convert host byte order to network order, then copy from hdr to dst */
    /* (note: argument 'hdr' is changed by the following code) */
    hdr->ulen  = htons(hdr->ulen);
    hdr->sport = htons(hdr->sport);
    hdr->dport = htons(hdr->dport);
    bcopy((char *)hdr, dst, HLEN);
}

static XkHandle aspPush(Sessn self, Msg *msg)
{
    SessnState *sstate = (SessnState *)self->state;
    ASPhdr     hdr;
    char       *buf;

    /* create a header by inserting length into header template */
    hdr = sstate->hdr;
    hdr.ulen = msgLength(msg) + HLEN;

    /* attach header to message and pass it on down the stack */
    buf = msgPush(msg, HLEN);
    aspHdrStore(&hdr, buf, HLEN);
    return xPush(xGetSessnDown(self, 0), msg);
}

static XkReturn
aspPop(Sessn self, Sessn lls, Msg *msg, void *hdr)
{
    ASPhdr *h = (ASPhdr *)hdr;

    /* truncate message to length shown in header */
    if (h->ulen - HLEN < msgLength(msg))
        msgTruncate(msg, (int)h->ulen);

    /* pass the message to the next protocol up the stack */
    return xDemux(xGetUp(self), self, msg);
}

static int
aspControlProtl(Protl self, int opcode, char *buf, int len)
{
    switch (opcode) {
        case GETMAXPACKET:
        case GETOPTPACKET:
            checkLen(len, sizeof(int));
            if (xControlProtl(xGetProtlDown(self, 0), opcode, buf, len) <
		    sizeof(int))
                return -1;
            *(int *)buf -= HLEN;
            return sizeof(int);
        default:
            return xControlProtl(xGetProtlDown(self, 0), opcode, buf, len);
    }
}

static int
aspControlSessn(Sessn self, int opcode, char *buf, int len)
{
    SessnState *sstate = (SessnState *)self->state;
    ASPhdr     *hdr;

    hdr = &(sstate->hdr);
    switch (opcode) {
        case GETMYPROTO:
            checkLen(len, sizeof(long));
            *(long *)buf = sstate->hdr.sport;
            return sizeof(long);
        case GETPEERPROTO:
            checkLen(len, sizeof(long));
            *(long *)buf = sstate->hdr.dport;
            return sizeof(long);
        case GETMAXPACKET:
        case GETOPTPACKET:
            checkLen(len, sizeof(int));
            if (xControlSessn(xGetSessnDown(self, 0), opcode, buf, len) <
		    sizeof(int))
                return -1;
            *(int *)buf -= HLEN;
            return sizeof(int);
        default:
            return xControlSessn(xGetSessnDown(self, 0), opcode, buf, len);
    }
}

static Part *
aspGetParticipants(Sessn self)
{
    Part       *p;
    int        numParts;
    SessnState *sstate = (SessnState *)self->state;
    long       localPort, remotePort;

    p = xGetParticipants(xGetSessnDown(self, 0));
    if (!p)
        return NULL;
    numParts = partLength(p);
    if (numParts > 0 && numParts <= 2) {
        if (numParts == 2) {
            localPort = (long)sstate->hdr.sport;
            partPush(p[1], (void *)&localPort, sizeof(long));
        }
        remotePort = (long)sstate->hdr.dport;
        partPush(p[0], (void *)&remotePort, sizeof(long));
	return p;
    }
    else    /* Bad number of participants */
        return NULL;
}

static XkReturn
aspClose(Sessn s)
{
     ProtlState *pstate = (ProtlState *)xMyProtl(s)->state;

     /* remove this session from the active map */
     mapRemoveBinding(pstate->activemap, s->binding);

     /* close the lower level session on which it depends */
     xClose(xGetSessnDown(s, 0));

     /* de-allocate the session object itself */
     xDestroySessn(s);

     return XK_SUCCESS;
}
