/* proto.c - Common protocol functions and structures */
 
/* Written 1995-1997 by Werner Almesberger, EPFL-LRC */
 

#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

#include "atmd.h"
#include "uni.h"
#include "qlib.h"
#include <q.out.h>

#include "io.h"
#include "proto.h"
#include "sap.h"


#define COMPONENT "SIGD"


const char *state_name[] = { /* formatting aligned with STATE */
	"<invalid>",	"ss_null",	"ss_listening",	"ss_connecting",
	"ss_connected",	"ss_indicated",	"ss_accepting",	"ss_zombie",
	"ss_wait_rel",	"ss_wait_close","ss_rel_req",	"ss_rel_ind",
	"ss_mod_lcl",	"ss_mod_req",	"ss_mod_rcv",	"ss_mod_fin_ack",
	"ss_mod_fin_ok","ss_mod_fin_fail"
	};

const char *cs_name[] = {
	"NULL",		"CALL_INIT",	"<invalid>",	"OUT_PROC",
	"<invalid>",	"<invalid>",	"CALL_PRES",	"<invalid>",
	"CONN_REQ",	"IN_PROC",	"ACTIVE",	"REL_REQ",
	"REL_IND",	"MOD_REQ",	"MOD_RCV" };

const char *as_name[] = { "<invalid>","as_bind","as_connect","as_accept",
  "as_reject","as_listen","as_okay","as_error","as_indicate","as_close",
  "as_itf_notify","as_modify" };

const CALL_STATE state_map[] = { /* formatting aligned with STATE */
	cs_null,	cs_null,	cs_null,	cs_call_init,
	cs_active,	cs_in_proc,	cs_conn_req,	cs_null,
	cs_rel_req,	cs_null,	cs_rel_req,	cs_rel_ind,
#ifdef Q2963_1
	cs_active,	cs_mod_req,	cs_mod_rcv,	cs_active,
	cs_active,	cs_active
#endif
	};

const PARTY_STATE eps_map[] = {
	ps_null,	ps_add_init,	ps_null,	ps_add_init,	/* 0 */
	ps_null,	ps_null,	ps_add_recv,	ps_null,	/* 4 */
	ps_active,	ps_add_recv,	ps_active,	ps_active,	/* 8 */
	ps_active };							/*12 */

SOCKET *sockets = NULL;
unsigned char q_buffer[MAX_Q_MSG];


SOCKET *new_sock(unsigned long id)
{
    SOCKET *sock;
 
    sock = alloc_t(SOCKET);
    sock->state = ss_invalid;
    sock->pvc.sap_addr.vpi = sock->pvc.sap_addr.vci = 0;
    sock->qos.txtp.traffic_class = sock->qos.rxtp.traffic_class = ATM_UBR;
    sock->id = id;
    memset(&sock->local,0,sizeof(sock->local));
    memset(&sock->remote,0,sizeof(sock->remote));
    memset(&sock->sap,0,sizeof(sock->sap));
    sock->error = 0;
    sock->call_state = cs_null;
    sock->ep_ref = -1;
    sock->conn_timer = NULL;
    sock->listen = NULL;
    sock->next = sockets;
    sockets = sock;
    return sock;
}


void free_sock(SOCKET *sock)
{
    SOCKET **walk;

    diag(COMPONENT,DIAG_DEBUG,"freeing socket 0x%lx@%p",sock->id,sock);
    for (walk = &sockets; *walk != sock; walk = &(*walk)->next);
    if (!*walk)
	diag(COMPONENT,DIAG_FATAL,
	  "INTERNAL ERROR: freeing non-existing socket 0x%lx",sock->id);
    *walk = sock->next;
    if (sock->conn_timer) {
	diag(COMPONENT,DIAG_ERROR,"socket 0x%lx has timer (%p) running",
	  sock->id,sock->conn_timer->callback);
	stop_timer(sock->conn_timer);
    }
    if (sock->listen)
        diag(COMPONENT,DIAG_ERROR,"socket 0x%lx has non-empty listen queue",
	  sock->id);
    sock->state = ss_invalid;
    free(sock);
}


void new_state(SOCKET *sock,STATE state)
{
    diag(COMPONENT,DIAG_DEBUG,"socket 0x%lx enters state %s (UNI %s)",
      sock->id,state_name[state],cs_name[state_map[state]]);
    sock->state = state;
    sock->call_state = state_map[state];
}


SOCKET *lookup_sap(const struct sockaddr_atmsvc *addr,
  const struct atm_sap *sap,const struct atm_qos *qos,
  struct sockaddr_atmsvc *res_addr,struct atm_sap *res_sap,
  struct atm_qos *res_qos,int exact_match)
{
    SOCKET *walk,*wildcard;
    int new_wc;

    new_wc = !atmsvc_addr_in_use(*addr);
    wildcard = NULL;
    for (walk = sockets; walk; walk = walk->next)
	if (walk->state == ss_listening && sap_compat(&walk->local,
	  addr,res_addr,&walk->sap,sap,res_sap,&walk->qos,qos,res_qos))
	    if (atmsvc_addr_in_use(walk->local)) return walk;
	    else if (exact_match) {
		    if (new_wc) return walk;
		}
		else wildcard = walk;
    return wildcard;
}


const char *mid2name(unsigned char mid)
{
    switch (mid) {
	case ATM_MSG_NATIONAL:
	    return "National specific message escape";
	case ATM_MSG_SETUP:
	    return "SETUP";
	case ATM_MSG_ALERTING:
	    return "ALERTING";
	case ATM_MSG_CALL_PROC:
	    return "CALL_PROCEEDING";
	case ATM_MSG_CONNECT:
	    return "CONNECT";
	case ATM_MSG_CONN_ACK:
	    return "CONNECT_ACK";
	case ATM_MSG_RESTART:
	    return "RESTART";
	case ATM_MSG_RELEASE:
	    return "RELEASE";
	case ATM_MSG_REST_ACK:
	    return "REST_ACK";
	case ATM_MSG_REL_COMP:
	    return "REL_COMP";
	case ATM_MSG_NOTIFY:
	    return "NOTIFY";
	case ATM_MSG_STATUS_ENQ:
	    return "STATUS_ENQ";
	case ATM_MSG_STATUS:
	    return "STATUS";
	case ATM_MSG_ADD_PARTY:
	    return "ADD_PARTY";
	case ATM_MSG_ADD_PARTY_ACK:
	    return "ADD_PARTY_ACK";
	case ATM_MSG_ADD_PARTY_REJ:
	    return "ADD_PARTY_REJECT";
	case ATM_MSG_PARTY_ALERT:
	    return "PARTY_ALERTING";
	case ATM_MSG_DROP_PARTY:
	    return "DROP_PARTY";
	case ATM_MSG_DROP_PARTY_ACK:
	    return "DROP_PARTY_ACK";
	case ATM_MSG_MODIFY_REQ:
	    return "MODIFY_REQUEST";
	case ATM_MSG_MODIFY_ACK:
	    return "MODIFY_ACK";
	case ATM_MSG_MODIFY_REJ:
	    return "MODIFY_REJECT";
	case ATM_MSG_CONN_AVAIL:
	    return "CONN_AVAIL";
	case ATM_MSG_LEAF_FAILURE:
	    return "LEAF SETUP FAIL";
	case ATM_MSG_LEAF_REQUEST:
	    return "LEAF SETUP REQ";
	case ATM_MSG_RESERVED:
	    return "Reserved...";
	default:
	    return "???";
    }
}


void send_kernel(unsigned long vcc,unsigned long listen_vcc,
  enum atmsvc_msg_type type,int reply,const struct sockaddr_atmpvc *pvc,
  const struct sockaddr_atmsvc *svc,const struct sockaddr_atmsvc *local,
  const struct atm_sap *sap,const struct atm_qos *qos)
{
    struct atmsvc_msg *msg;

    msg = alloc_t(struct atmsvc_msg);
    msg->vcc = vcc;
    msg->listen_vcc = listen_vcc;
    msg->type = type;
    msg->reply = reply;
    if (pvc) msg->pvc = *pvc;
    else memset(&msg->pvc,0,sizeof(msg->pvc));
    if (sap) msg->sap = *sap;
    else memset(&msg->sap,0,sizeof(msg->sap));
    if (qos) msg->qos = *qos;
    else memset(&msg->qos,0,sizeof(msg->qos));
    if (local) msg->local = *local;
    else memset(&msg->local,0,sizeof(msg->local));
    if (svc) msg->svc = *svc;
    else memset(&msg->svc,0,sizeof(msg->svc));
    to_kernel(msg);
    free(msg);
}


void send_release(SOCKET *sock,unsigned char reason,...)
{
    va_list ap;
    Q_DSC dsc;
    int size;
 
    q_create(&dsc,q_buffer,MAX_Q_MSG);
    q_assign(&dsc,QF_msg_type,ATM_MSG_RELEASE);
    q_assign(&dsc,QF_call_ref,sock->call_ref);
    q_assign(&dsc,QF_cause,reason);
    va_start(ap,reason);
    switch (reason) {
	case ATM_CV_TIMER_EXP:
	    {
		char buf[4];

		sprintf(buf,"%d",va_arg(ap,int));
		q_write(&dsc,QF_timer,buf,3);
		break;
	    }
	default:
    }
    va_end(ap);
    if ((size = q_close(&dsc)) >= 0) to_signaling(q_buffer,size);
}


void send_release_complete(unsigned long call_ref,unsigned char cause)
{
    Q_DSC dsc;
    int size;
 
    q_create(&dsc,q_buffer,MAX_Q_MSG);
    q_assign(&dsc,QF_msg_type,ATM_MSG_REL_COMP);
    q_assign(&dsc,QF_call_ref,call_ref);
    if (cause) q_assign(&dsc,QF_cause,cause); /* @@@ more data */
    if ((size = q_close(&dsc)) >= 0) to_signaling(q_buffer,size);
}


void send_modify_reject(SOCKET *sock,unsigned char reason)
{
    Q_DSC dsc;
    int size;
 
    q_create(&dsc,q_buffer,MAX_Q_MSG);
    q_assign(&dsc,QF_msg_type,ATM_MSG_MODIFY_REJ);
    q_assign(&dsc,QF_call_ref,sock->call_ref);
    q_assign(&dsc,QF_cause,reason);
    if ((size = q_close(&dsc)) >= 0) to_signaling(q_buffer,size);
}


void set_error(SOCKET *sock,int code)
{
    if (!sock->error) sock->error = code;
}


void send_close(SOCKET *sock)
{
    if (sock->error == 1234) diag(COMPONENT,DIAG_ERROR,"BUG! BUG! BUG!");
    send_kernel(sock->id,0L,as_close,sock->error,NULL,NULL,NULL,NULL,NULL);
    sock->error = 1234;
}


void q_report(int severity,const char *msg,...)
{
    va_list ap;

    va_start(ap,msg);
    vdiag("QMSG",severity,msg,ap);
    va_end(ap);
}


int get_vci(int itf)
{
    SOCKET *walk;
    int s,vci;

    s = get_pvc(itf,&vci);
    if (s < 0) return s;
    for (walk = sockets; walk; walk = walk->next)
	if (walk->pvc.sap_addr.vci == vci) {
	    vci = get_vci(itf); /* this recursion will keep all the busy ones
				   open until we return */
	    break;
	}
    (void) close(s);
    return vci;
}


typedef struct _vpci {
    int vpci;
    int itf;
    struct _vpci *next;
} VPCI;


static VPCI *vpcis = NULL;


void enter_vpci(int vpci,int itf)
{
    VPCI *entry;

    for (entry = vpcis; entry; entry = entry->next)
	if (entry->vpci == vpci) {
	    diag(COMPONENT,DIAG_ERROR,"ignoring duplicate VPCI %d (itf %d)",
	      vpci,itf);
	    return;
	}
    entry = alloc_t(VPCI);
    entry->vpci = vpci;
    entry->itf = itf;
    entry->next = vpcis;
    vpcis = entry;
}


int get_itf(int *vpci)
{
    VPCI *best,*walk;

    best = NULL;
    for (walk = vpcis; walk; walk = walk->next)
	if (walk->vpci <= *vpci && (!best || best->vpci < walk->vpci))
	    best = walk;
    if (!best) return signaling_pvc.sap_addr.itf;
    *vpci -= best->vpci;
    return best->itf;
}


void init_addr(void)
{
    VPCI *walk;

    itf_load(signaling_pvc.sap_addr.itf);
    for (walk = vpcis; walk; walk = walk->next) itf_load(walk->itf);
}
