/* kernel.c - Processing of incoming kernel messages */
 
/* Written 1995-1997 by Werner Almesberger, EPFL-LRC */
 

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <linux/atm.h>
#include <linux/atmsvc.h>
#include <linux/atmdev.h>

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

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


#define COMPONENT "KERNEL"


static int send_setup(SOCKET *sock)
{
    static unsigned long call_ref = 0;
    struct sockaddr_atmsvc *local;
    SOCKET *walk;
    Q_DSC dsc;
    int error,size;

    do {
	if (++call_ref == 0x7fffff) call_ref = 1;
	for (walk = sockets; walk; walk = walk->next)
	    if (walk->call_ref == call_ref) break;
    }
    while (walk);
    sock->call_ref = call_ref;
    q_create(&dsc,q_buffer,MAX_Q_MSG);
    q_assign(&dsc,QF_call_ref,call_ref);
    q_assign(&dsc,QF_msg_type,ATM_MSG_SETUP);
    q_assign(&dsc,QF_aal_type,5); /* AAL 5 */
#ifdef UNI30
    q_assign(&dsc,QF_aal_mode,1); /* Message mode - LANE seems to really want
				     this */
#endif
    q_assign(&dsc,QF_sscs_type,0); /* unspecified - LANE wants this */
    error = sap_encode(&dsc,&sock->remote,&sock->sap,&sock->qos);
    q_assign(&dsc,QF_bearer_class,16); /* BCOB-X */
#if defined(UNI30) || defined(ALLOW_UNI30)
    q_assign(&dsc,QF_trans_cap,ATM_TC_VBR_NRT_R00);
	/* force presence - UNI 3.0 wants this */
#endif
    q_assign(&dsc,QF_upcc,0); /* p2p */
#ifndef UNI30
    q_assign(&dsc,QF_qos_cs,Q2931_CS_ITU);
#endif
    q_assign(&dsc,QF_qos_fw,0); /* QOS 0 */
    q_assign(&dsc,QF_qos_bw,0);
    local = NULL;
    if (atmsvc_addr_in_use(sock->local)) local = &sock->local;
    else if (local_addr[0].state == ls_same &&
	  atmsvc_addr_in_use(local_addr[0].addr))
	    local = &local_addr[0].addr;
    if (!local) {
	if (!error) error = -EADDRNOTAVAIL;
    }
    else if (*local->sas_addr.pub) {
	    q_assign(&dsc,QF_cgpn_plan,ATM_NP_E164);
	    q_assign(&dsc,QF_cgpn_type,ATM_TON_INTRNTNL);
	    q_write(&dsc,QF_cgpn,(void *) local->sas_addr.pub,
	      strlen(local->sas_addr.pub));
	}
	else {
	    q_assign(&dsc,QF_cgpn_plan,ATM_NP_AEA);
	    q_assign(&dsc,QF_cgpn_type,ATM_TON_UNKNOWN);
	    q_write(&dsc,QF_cgpn,(void *) local->sas_addr.prv,ATM_ESA_LEN);
	}
    if (net) {
	int vci;

	vci = get_vci(0);
	if (vci < 0) {
	    (void) q_close(&dsc);
	    return vci;
	}
	q_assign(&dsc,QF_vpi,0); /* @@@ */
	q_assign(&dsc,QF_vci,vci);
	sock->pvc.sap_addr.itf = get_itf(0);
	sock->pvc.sap_addr.vpi = 0;
	sock->pvc.sap_addr.vci = vci;
    }
    if ((size = q_close(&dsc)) < 0) error = -EINVAL;
    else if (!error) to_signaling(q_buffer,size);
    return error;
}


static int send_connect(SOCKET *sock)
{
    Q_DSC dsc;
    int size;

    q_create(&dsc,q_buffer,MAX_Q_MSG);
    q_assign(&dsc,QF_msg_type,ATM_MSG_CONNECT);
    q_assign(&dsc,QF_call_ref,sock->call_ref);
    if (sock->ep_ref >= 0) q_assign(&dsc,QF_ep_ref,sock->ep_ref);
    q_assign(&dsc,QF_aal_type,5);
#ifdef UNI30
    q_assign(&dsc,QF_aal_mode,1); /* Message mode - LANE seems to really want
				     this */
#endif
    q_assign(&dsc,QF_sscs_type,0); /* unspecified - LANE wants this */
    q_assign(&dsc,QF_fw_max_sdu,sock->qos.rxtp.max_sdu);
    q_assign(&dsc,QF_bw_max_sdu,sock->qos.txtp.max_sdu);
    if ((size = q_close(&dsc)) >= 0) to_signaling(q_buffer,size);
    return 0;
}


#ifdef Q2963_1

static void send_modify_request(SOCKET *sock)
{
    Q_DSC dsc;
    int size;

    q_create(&dsc,q_buffer,MAX_Q_MSG);
    q_assign(&dsc,QF_msg_type,ATM_MSG_MODIFY_REQ);
    q_assign(&dsc,QF_call_ref,sock->call_ref);
    if (sock->new_qos.txtp.traffic_class)
	q_assign(&dsc,QF_fw_pcr_01,SELECT_TOP_PCR(sock->new_qos.txtp));
    if (sock->new_qos.rxtp.traffic_class)
	q_assign(&dsc,QF_bw_pcr_01,SELECT_TOP_PCR(sock->new_qos.rxtp));
    if ((size = q_close(&dsc)) >= 0) to_signaling(q_buffer,size);
}


static void send_modify_ack(SOCKET *sock)
{
    Q_DSC dsc;
    int size;

    q_create(&dsc,q_buffer,MAX_Q_MSG);
    q_assign(&dsc,QF_msg_type,ATM_MSG_MODIFY_ACK);
    q_assign(&dsc,QF_call_ref,sock->call_ref);
    if (sock->qos.rxtp.traffic_class != ATM_NONE &&
      sock->qos.rxtp.max_pcr < sock->new_qos.rxtp.max_pcr) {
	q_assign(&dsc,QF_type_of_report,ATM_TOR_MOD_CONF);
	START_TIMER(sock,T361);
    }
    if ((size = q_close(&dsc)) >= 0) to_signaling(q_buffer,size);
}

#endif


static int bind_check(SOCKET *sock,struct sockaddr_atmsvc *local)
{
    struct sockaddr_atmsvc *use;

    if (atmsvc_addr_in_use(sock->local)) return 0;
    if (atmsvc_addr_in_use(*local)) use = local;
    else {
	if (local_addr[0].state != ls_same) {
	    diag(COMPONENT,DIAG_ERROR,"no local address");
	    return -EADDRNOTAVAIL;
	}
	use = &local_addr[0].addr;
    }
    sock->local = *use;
    return 0;
}


static void dispatch(SOCKET *sock,struct atmsvc_msg *msg)
{
    int error;

    switch (msg->type) {
	case as_bind: /* only in NULL state */
	    if (sock) break;
	    if (msg->svc.sas_family != AF_ATMSVC) {
		SEND_ERROR(msg->vcc,-EAFNOSUPPORT);
		return;
	    }
	    if (!atmsvc_addr_in_use(msg->svc))
#ifdef BE_PICKY_ABOUT_BINDING_LOCAL_WILDCARD_ADDRESSES
		if (local_addr[0].state != ls_same)
		    SEND_ERROR(msg->vcc,-EADDRNOTAVAIL);
		else
#endif
		send_kernel(msg->vcc,0,as_okay,0,NULL,NULL,
		      &local_addr[0].addr,NULL,NULL);
	    else {
		LOCAL_ADDR *walk;

		for (walk = local_addr; walk->state != ls_unused; walk++)
		    if (walk->state == ls_same && atm_equal(&walk->addr,
		      &msg->svc,(ATM_ESA_LEN-1)*8,AXE_WILDCARD)) break;
		    if(*msg->svc.sas_addr.pub)
			diag(COMPONENT,DIAG_DEBUG,"binding to E.164 address "
			  "%s\n",msg->svc.sas_addr.pub);
		if (walk->state == ls_unused)
		    SEND_ERROR(msg->vcc,-EADDRNOTAVAIL);
		else send_kernel(msg->vcc,0,as_okay,0,NULL,NULL,NULL,NULL,NULL);
	    }
	    return;
	case as_connect: /* NULL state only */
	    if (sock) break;
	    if (!allow(&msg->svc,ACL_OUT)) {
		SEND_ERROR(msg->vcc,-EACCES);
		return;
	    }
	    sock = new_sock(msg->vcc);
	    error = bind_check(sock,&msg->local);
	    if (error) {
		free_sock(sock);
		SEND_ERROR(msg->vcc,error);
		return;
	    }
	    sock->remote = msg->svc;
	    sock->qos = msg->qos;
	    sock->sap = msg->sap;
	    sock->state = ss_connecting;
	    error = send_setup(sock);
	    if (error) {
		SEND_ERROR(msg->vcc,error);
		free_sock(sock);
		return;
	    }
	    START_TIMER(sock,T303);
	    new_state(sock,ss_connecting);
	    return;
	case as_accept:
	    if (sock->state == ss_zombie) {
		SEND_ERROR(msg->vcc,-ECONNRESET); /* -ERESTARTSYS ? */
		free_sock(sock);
		return;
	    }
	    if (sock->state != ss_indicated) break;
	    error = send_connect(sock);
	    if (!error) {
		START_TIMER(sock,T313);
		new_state(sock,ss_accepting);
		return;
	    }
	    SEND_ERROR(sock->id,error);
	    send_release(sock,0); /* @@@ */
	    START_TIMER(sock,T308_1);
	    new_state(sock,ss_wait_rel);
	    return;
	case as_reject: /* ZOMBIE or INDICATED */
	    if (sock->state == ss_zombie) {
		free_sock(sock);
		return;
	    }
	    if (sock->state != ss_indicated) break;
	    send_release(sock,ATM_CV_CALL_REJ); /* @@@ should use msg->reply */
	    START_TIMER(sock,T308_1);
	    new_state(sock,ss_wait_rel);
	    return;
	case as_listen: /* NULL */
	    if (sock) break;
	    if (msg->svc.sas_family != AF_ATMSVC) {
		SEND_ERROR(msg->vcc,-EAFNOSUPPORT);
		return;
	    }
	    if (msg->qos.aal != ATM_AAL5) {
		SEND_ERROR(msg->vcc,-EINVAL);
		return;
	    }
	    if (lookup_sap(&msg->svc,&msg->sap,&msg->qos,NULL,NULL,NULL,1)) {
		SEND_ERROR(msg->vcc,-EADDRINUSE);
		return;
	    }
	    sock = new_sock(msg->vcc);
	    sock->local = msg->svc;
	    sock->sap = msg->sap;
	    sock->qos = msg->qos;
	    send_kernel(sock->id,0,as_okay,0,NULL,NULL,NULL,NULL,NULL);
	    sock->state = ss_listening;
	    return;
	case as_close: /* all but INDICATED, ZOMBIE, and WAIT_REL */
	    if (sock && (sock->state == ss_indicated || sock->state ==
	      ss_zombie || sock->state == ss_wait_rel)) break;
	    switch (sock ? sock->state : ss_null) {
		case ss_listening:
		case ss_zombie:
		    send_close(sock);
		    /* fall through */
		case ss_wait_close:
		    free_sock(sock);
		    /* fall through */
		case ss_null:
		case ss_rel_req:
		    return;
		case ss_connecting:
		case ss_accepting:
#ifdef Q2963_1
		case ss_mod_req:
#endif
		    STOP_TIMER(sock);
		    /* fall through */
#ifdef Q2963_1
		case ss_mod_lcl:
		case ss_mod_rcv:
		case ss_mod_fin_ok:
		case ss_mod_fin_fail:
		case ss_mod_fin_ack:
#endif
		case ss_connected:
#ifdef Q2963_1
		    if (timer_handler(sock->conn_timer) == on_T361)
			STOP_TIMER(sock);
#endif
		    if (sock->state == ss_connected)
			diag(COMPONENT,DIAG_INFO,"Active close (CR 0x%06X)",
			  sock->call_ref);
		    send_release(sock,
#if defined(UNI31) || defined(UNI40)
		      ATM_CV_NORMAL_CLEAR
#else
		      ATM_CV_NORMAL_UNSPEC
#endif
		      );
		    START_TIMER(sock,T308_1);
		    new_state(sock,ss_rel_req);
		    return;
		case ss_rel_ind:
		    send_release_complete(sock->call_ref,0); /* @@@ */
		    free_sock(sock);
		    return;
		default:
		    break;
	    }
	    break;
#ifdef Q2963_1
	case as_modify:
	    if (sock && (sock->state == ss_mod_lcl ||
	      sock->state == ss_mod_req || sock->state == ss_mod_rcv ||
	      sock->state == ss_mod_fin_ok || sock->state == ss_mod_fin_fail ||
	      sock->state == ss_mod_fin_ack)) {
		send_kernel(sock->id,0,as_okay,-EALREADY,NULL,NULL,NULL,NULL,
		  NULL);
		return;
	    }
	    if (!sock || sock->state != ss_connected) {
		send_kernel(sock->id,0,as_okay,-EBADFD,NULL,NULL,NULL,NULL,
		  NULL);
		return;
	    }
	    if (sock->qos.txtp.traffic_class != msg->qos.txtp.traffic_class ||
	      sock->qos.rxtp.traffic_class != msg->qos.rxtp.traffic_class) {
	/* @@@ may do more checking */
		send_kernel(sock->id,0,as_okay,-EINVAL,NULL,NULL,NULL,NULL,
		  NULL);
		return;
	    }
	    sock->new_qos = msg->qos;
	    send_kernel(sock->id,0,as_modify,ATM_MF_INC_RSV | ATM_MF_DEC_SHP,
	      NULL,NULL,NULL,NULL,&msg->qos);
	    new_state(sock,ss_mod_lcl);
	    return;
	case as_okay:
	    switch (sock ? sock->state : ss_null) {
		case ss_mod_lcl:
		    send_modify_request(sock);
		    START_TIMER(sock,T360);
		    new_state(sock,ss_mod_req);
		    return;
		case ss_mod_rcv:
		    send_modify_ack(sock);
		    sock->qos = sock->new_qos;
		    new_state(sock,ss_connected);
		    return;
		case ss_mod_fin_ok:
		    send_kernel(sock->id,0,as_okay,0,NULL,NULL,NULL,NULL,NULL);
		    new_state(sock,ss_connected);
		    return;
		case ss_mod_fin_fail:
		    send_kernel(sock->id,0,as_okay,sock->error,NULL,NULL,NULL,
		      NULL,NULL);
		    sock->error = 0;
		    /* fall through */
		case ss_mod_fin_ack:
		    new_state(sock,ss_connected);
		    /* fall through */
		default:
		    return; /* ignore stray as_okay */
	    }
	case as_error:
	    switch (sock ? sock->state : ss_null) {
		case ss_mod_lcl:
		    send_kernel(sock->id,0,as_okay,msg->reply,NULL,NULL,NULL,
		      NULL,NULL);
		    new_state(sock,ss_connected);
		    return;
		case ss_mod_rcv:
		    send_modify_reject(sock,ATM_CV_RES_UNAVAIL);
		    new_state(sock,ss_connected);
		    return;
		case ss_mod_fin_ok:
		    diag(COMPONENT,DIAG_ERROR,"QOS commit failed");
		    send_kernel(sock->id,0,as_okay,0,NULL,NULL,NULL,NULL,NULL);
		    /* @@@ clear call instead ? */
		    new_state(sock,ss_connected);
		    return;
		case ss_mod_fin_fail:
		    diag(COMPONENT,DIAG_ERROR,"QOS rollback failed");
		    send_kernel(sock->id,0,as_okay,sock->error,NULL,NULL,NULL,
		      NULL,NULL);
		    sock->error = 0;
		    /* @@@ clear call instead ? */
		    new_state(sock,ss_connected);
		    return;
		case ss_mod_fin_ack:
		    /* @@@ maybe we should even clear the call now */
		    diag(COMPONENT,DIAG_ERROR,"QOS commit failed");
		    new_state(sock,ss_connected);
		    return;
		default:
		    return; /* ignore stray as_error */
	    }
#endif
	default:
    	    diag(COMPONENT,DIAG_WARN,"invalid message %d",(int) msg->type);
	    return;
    }
    diag(COMPONENT,DIAG_WARN,"message %s is incompatible with state %s (%d)",
      as_name[msg->type],state_name[sock ? sock->state : ss_null],
      (int) (sock ? sock->state : ss_null));
}


static void dispatch_listen(SOCKET *sock,struct atmsvc_msg *msg)
{
    SOCKET *next;

    if (!sock) {
	diag(COMPONENT,DIAG_WARN,"message %s is incompatible with state %s "
	  "(%d)",as_name[msg->type],state_name[ss_null],ss_null);
	return;
    }
    if (!(next = sock->listen)) {
	diag(COMPONENT,DIAG_WARN,
	  "socket 0x%lx got accept with empty listen queue",msg->vcc);
	return;
    }
    sock->listen = next->listen;
    next->listen = NULL;
    next->id = msg->vcc;
    dispatch(next,msg);
}


void itf_load(int itf)
{
    char buf[MAX_ATM_ADDR_LEN+1];
    LOCAL_ADDR *walk;

    (void) get_addr(itf);
    for (walk = local_addr; walk->state != ls_unused; walk++)
	if (walk->itf == itf) {
	    if (atm2text(buf,MAX_ATM_ADDR_LEN+1,(struct sockaddr *)
	      &walk->addr,pretty) < 0) strcpy(buf,"<invalid>");
	    switch (walk->state) {
		case ls_added:
		    diag(COMPONENT,DIAG_INFO,"Added local ATM address %s at "
		      "itf %d",buf,itf);
		    walk->state = ls_same;
		    break;
		case ls_removed:
		    diag(COMPONENT,DIAG_INFO,"Removed local ATM address %s at "
		      "itf %d",buf,itf);
		    walk->state = ls_unused;
		    /* @@@ delete SVCs using that address ? */
		    break;
		default:
		    break;
	    }
	}
}


void from_kernel(struct atmsvc_msg *msg,int size)
{
    void (*dispatcher)(SOCKET *,struct atmsvc_msg *);
    SOCKET *curr;

    if (msg->type == as_itf_notify) {
	itf_load(msg->pvc.sap_addr.itf);
	return;
    }
    if (msg->type != as_accept && msg->type != as_reject) {
	dispatcher = dispatch;
	for (curr = sockets; curr; curr = curr->next)
	    if (msg->vcc == curr->id) break;
    }
    else {
	dispatcher = dispatch_listen;
	for (curr = sockets; curr; curr = curr->next)
	    if (msg->listen_vcc == curr->id && curr->state == ss_listening)
		break;
    }
    diag(COMPONENT,DIAG_DEBUG,"FROM KERNEL: %s for socket %p (0x%lx/0x%lx) "
      "in state %s",as_name[msg->type],curr,msg->vcc,msg->listen_vcc,
      state_name[curr ? curr->state : ss_null]);
    dispatcher(curr,msg);
}
