/* io.c - I/O operations */
 
/* Written 1995-1997 by Werner Almesberger, EPFL-LRC */
 

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <linux/atm.h>
#include <linux/atmclip.h> /* for CLIP_DEFAULT_IDLETIMER */
#include <linux/atmarp.h>
#include <linux/if_arp.h>

#include "atm.h"
#include "atmd.h"

#include "table.h"
#include "arp.h"
#include "itf.h"
#include "io.h"


#define COMPONENT "IO"


struct timeval now;


static int kernel,incoming,inet;


/* ----- kernel interface -------------------------------------------------- */


void open_all(void)
{
    struct sockaddr_atmsvc addr;
    struct atm_qos qos;
    struct atm_sap sap;

    if ((kernel = socket(PF_ATMSVC,SOCK_DGRAM,0)) < 0)
	diag(COMPONENT,DIAG_FATAL,"socket: %s",strerror(errno));
    if (ioctl(kernel,ATMARPD_CTRL,0) < 0)
	diag(COMPONENT,DIAG_FATAL,"ioctl ATMARPD_CTRL: %s",strerror(errno));
    if ((incoming = socket(PF_ATMSVC,SOCK_DGRAM,0)) < 0)
	diag(COMPONENT,DIAG_FATAL,"socket: %s",strerror(errno));
    memset(&qos,0,sizeof(qos));
    qos.aal = ATM_AAL5;
    qos.rxtp.traffic_class = qos.txtp.traffic_class = ATM_ANYCLASS;
    if (setsockopt(incoming,SOL_ATM,SO_ATMQOS,&qos,sizeof(qos)) < 0)
	diag(COMPONENT,DIAG_FATAL,"setsockopt SO_ATMQOS: %s",strerror(errno));
    memset(&sap,0,sizeof(sap));
    sap.blli[0].l2_proto = ATM_L2_ISO8802;
    sap.blli[0].l3_proto = ATM_L3_NONE;
    if (setsockopt(incoming,SOL_ATM,SO_ATMSAP,&sap,sizeof(sap)) < 0)
	diag(COMPONENT,DIAG_FATAL,"setsockopt SO_ATMSAP: %s",strerror(errno));
    memset(&addr,0,sizeof(addr));
    addr.sas_family = AF_ATMSVC;
    if (bind(incoming,(struct sockaddr *) &addr,sizeof(addr)) >= 0) {
	if (listen(incoming,5) < 0)
	    diag(COMPONENT,DIAG_FATAL,"listen: %s",strerror(errno));
    }
    else if (errno != EUNATCH)
	    diag(COMPONENT,DIAG_FATAL,"bind: %s",strerror(errno));
	else {
	    diag(COMPONENT,DIAG_WARN,"SVCs are not available");
	    (void) close(incoming);
	    incoming = -1;
	}
    if ((inet = socket(PF_INET,SOCK_DGRAM,0)) < 0)
	diag(COMPONENT,DIAG_FATAL,"socket: %s",strerror(errno));
}


#if 1
#define KERNEL_BUFFER_SIZE 500
#else
#define KERNEL_BASE_LEN (sizeof(struct atmsvc_msg)-sizeof(struct atm_blli))
#define KERNEL_BUFFER_SIZE (sizeof(struct atmsvc_msg)+(ATM_MAX_BLLI-1)* \
  sizeof(struct atm_blli)+1)
#endif


static void recv_kernel(void)
{
    struct atmarp_ctrl *ctrl;
    unsigned char buffer[KERNEL_BUFFER_SIZE];
    int size;

    size = read(kernel,buffer,KERNEL_BUFFER_SIZE);
    if (size < 0) {
	diag(COMPONENT,DIAG_ERROR,"read kernel: %s",strerror(errno));
	return;
    }
    ctrl = (struct atmarp_ctrl *) buffer;
    if (ctrl->magic != ATMARP_CTRL_MAGIC) {
	diag(COMPONENT,DIAG_ERROR,"invalid magic number (0x%x) in kernel msg",
	  ctrl->magic);
	return;
    }
    switch (ctrl->type) {
	case act_need:
	    need_ip(ctrl->itf_num,ctrl->arg);
	    break;
	case act_create:
	    ctrl->type = act_complete;
	    ctrl->arg = itf_create(ctrl->itf_num);
	    if (write(kernel,ctrl,sizeof(*ctrl)) < 0)
		diag(COMPONENT,DIAG_ERROR,"write reply: %s",strerror(errno));
	    break;
	case act_up:
	    itf_up(ctrl->itf_num);
	    break;
	case act_down:
	    itf_down(ctrl->itf_num);
	    break;
	case act_ioctl:
	    ctrl->type = act_complete;
	    ctrl->arg = arp_ioctl(ctrl->itf_num,ctrl->arg,(struct atmarpreq *)
	      ctrl->data);
	    if (write(kernel,ctrl,sizeof(*ctrl)) < 0)
		diag(COMPONENT,DIAG_ERROR,"write reply: %s",strerror(errno));
	    break;
	default:
	    diag(COMPONENT,DIAG_ERROR,"invalid control msg type 0x%x",
	      ctrl->type);
    }
}


void close_all(void)
{
    if (incoming >= 0) (void) close(incoming);
    (void) close(kernel); /* may get major complaints from the kernel ... */
    (void) close(inet);
}


/* ----- common part ------------------------------------------------------- */


#define MAX_BUFFER 1024


static fd_set rset,cset;


int do_close(int fd)
{
    int result;

    result = close(fd);
    FD_CLR(fd,&rset); /* we might open a new fd with the same number, so ... */
    FD_CLR(fd,&cset);
    return result;
}


static void recv_vcc(VCC *vcc)
{
    unsigned char buffer[MAX_BUFFER];
    int size;

    size = read(vcc->fd,buffer,MAX_BUFFER);
    if (!size) {
	disconnect_vcc(vcc);
	return;
    }
    if (size < 0) {
	diag(COMPONENT,DIAG_ERROR,"read vcc: %s",strerror(errno));
	disconnect_vcc(vcc);
	return;
    }
    if (debug) {
	int i;
	for (i = 0; i < size; i++) printf("%02X ",buffer[i]);
	printf("\n");
    }
    incoming_arp(vcc,(struct atmarphdr *) buffer,size);
}


static void accept_new(void)
{
    char buffer[MAX_ATM_ADDR_LEN+1];
    struct sockaddr_atmsvc addr;
    ENTRY *entry;
    VCC *vcc;
    int fd,len,error;

    len = sizeof(addr);
    if ((fd = accept(incoming,(struct sockaddr *) &addr,&len)) < 0) {
	error = errno;
	diag(COMPONENT,DIAG_ERROR,"accept: %s",strerror(errno));
	if (error == EUNATCH) {
	    diag(COMPONENT,DIAG_WARN,"disabling SVCs");
	    (void) close(incoming);
	    incoming = -1;
	}
	return;
    }
    if (ioctl(fd,ATMARP_MKIP,CLIP_DEFAULT_IDLETIMER) < 0) {
        diag(COMPONENT,DIAG_ERROR,"ioctl ATMARP_MKIP: %s",strerror(errno));
        (void) do_close(fd);
        return;
    }
    vcc = alloc_t(VCC);
    vcc->active = 0;
    vcc->connecting = 0;
    vcc->fd = fd;
    /* the following code probably belongs to arp.c ... */
    if (atm2text(buffer,MAX_ATM_ADDR_LEN+1,(struct sockaddr *) &addr,pretty) <
      0) strcpy(buffer,"<atm2text error>");
    diag(COMPONENT,DIAG_DEBUG,"Incoming call from %s",buffer);
    if (merge) {
	ITF *itf;

	for (itf = itfs; itf; itf = itf->next) {
	    entry = lookup_addr(itf,&addr);
	    if (entry) {
		vcc->entry = entry;
		Q_INSERT_HEAD(entry->vccs,vcc);
		if (entry->state == as_valid)
		    if (set_ip(vcc->fd,entry->ip) < 0)
			diag(COMPONENT,DIAG_ERROR,"set_ip: %s",
			  strerror(errno));
		return;
	    }
	}
    }
    entry = alloc_entry(1);
    entry->state = as_invalid;
    entry->addr = alloc_t(struct sockaddr_atmsvc);
    *entry->addr = addr;
    entry->flags = ATF_PUBL;
    Q_INSERT_HEAD(unknown_incoming,entry);
    vcc->entry = entry;
    Q_INSERT_HEAD(entry->vccs,vcc);
    incoming_call(vcc);
}


int connect_vcc(struct sockaddr *remote,const struct atm_qos *qos,int timeout)
{
    int fd,flags;

    if (remote->sa_family == AF_ATMSVC && incoming < 0) return -EUNATCH;
    if ((fd = socket(remote->sa_family,SOCK_DGRAM,0)) < 0) {
	diag(COMPONENT,DIAG_ERROR,"socket: %s",strerror(errno));
	return -errno;
    }
    if (setsockopt(fd,SOL_ATM,SO_ATMQOS,qos,sizeof(*qos)) < 0) {
	diag(COMPONENT,DIAG_ERROR,"setsockopt SO_ATMQOS: %s",strerror(errno));
	return -errno;
    }
    if ((flags = fcntl(fd,F_GETFL)) < 0) {
	diag(COMPONENT,DIAG_ERROR,"fcntl F_GETFL: %s",strerror(errno));
	return -errno;
    }
    flags |= O_NONBLOCK;
    if (fcntl(fd,F_SETFL,flags) < 0) {
	diag(COMPONENT,DIAG_ERROR,"fcntl F_GETFL: %s",strerror(errno));
	return -errno;
    }
    if (remote->sa_family == AF_ATMSVC) { /* @@@ that's cheating */
	struct atm_sap sap;

	memset(&sap,0,sizeof(sap));
	sap.blli[0].l2_proto = ATM_L2_ISO8802;
	sap.blli[0].l3_proto = ATM_L3_NONE;
	if (setsockopt(fd,SOL_ATM,SO_ATMSAP,&sap,sizeof(sap)) < 0) {
	    diag(COMPONENT,DIAG_ERROR,"setsockopt SO_ATMSAP: %s",
	      strerror(errno));
	    return -errno;
	}
    }
    /* PVC connect never blocks */
    if (connect(fd,remote,remote->sa_family == AF_ATMPVC ?
      sizeof(struct sockaddr_atmpvc) : sizeof(struct sockaddr_atmsvc)) < 0) {
	if (errno != EINPROGRESS) {
	    diag(COMPONENT,DIAG_ERROR,"[1]connect: %s",strerror(errno));
	    return -errno;
	}
	return fd;
    }
    if (ioctl(fd,ATMARP_MKIP,timeout) < 0) {
        diag(COMPONENT,DIAG_ERROR,"ioctl ATMARP_MKIP: %s",strerror(errno));
        (void) do_close(fd);
        return -errno;
    }
    return fd;
}


int set_ip(int fd,int ip)
{
    if (ioctl(fd,ATMARP_SETENTRY,ip) >= 0) return 0;
    diag(COMPONENT,DIAG_ERROR,"ioctl ATMARP_SETENTRY: %s",strerror(errno));
    (void) do_close(fd);
    return -errno;
}


int set_encap(int fd,int mode)
{
    if (ioctl(fd,ATMARP_ENCAP,mode) >= 0) return 0;
    diag(COMPONENT,DIAG_ERROR,"ioctl ATMARP_ENCAP: %s",strerror(errno));
    (void) do_close(fd);
    return -errno;
}


static void complete_connect(VCC *vcc)
{
    struct sockaddr_atmsvc dummy;

    if (!vcc->connecting)
	diag(COMPONENT,DIAG_FATAL,"connecting non-connecting VCC 0x%08x",
	  (unsigned long) vcc);
    memset(&dummy,0,sizeof(dummy));
    if (!connect(vcc->fd,(struct sockaddr *) &dummy,sizeof(dummy))) {
	if (ioctl(vcc->fd,ATMARP_MKIP,CLIP_DEFAULT_IDLETIMER) < 0) {
	    diag(COMPONENT,DIAG_ERROR,"ioctl ATMARP_MKIP: %s",strerror(errno));
	    (void) do_close(vcc->fd);
	    vcc_failed(vcc);
	}
	vcc_connected(vcc);
    }
    else {
	diag(COMPONENT,DIAG_INFO,"[2]connect: %s",strerror(errno));
	(void) do_close(vcc->fd);
	vcc_failed(vcc);
    }
}


void poll_loop(void)
{
    ITF *itf,*next_itf;
    ENTRY *entry,*next_entry;
    VCC *vcc,*next_vcc;
    int fds,ret;

    gettimeofday(&now,NULL);
    while (1) {
	FD_ZERO(&rset);
	FD_ZERO(&cset);
	FD_SET(kernel,&rset);
	if (incoming >= 0) FD_SET(incoming,&rset);
	fds = (kernel > incoming ? kernel : incoming)+1;
	for (itf = itfs; itf; itf = itf->next)
	    for (entry = itf->table; entry; entry = entry->next)
		for (vcc = entry->vccs; vcc; vcc = vcc->next) {
		    if (vcc->connecting) FD_SET(vcc->fd,&cset);
		    else FD_SET(vcc->fd,&rset);
		    if (vcc->fd >= fds) fds = vcc->fd+1;
		}
	for (entry = unknown_incoming; entry; entry = entry->next) {
	    if (!entry->vccs || entry->vccs->next) {
		diag(COMPONENT,DIAG_ERROR,"internal error: bad unknown entry");
		continue;
	    }
	    FD_SET(entry->vccs->fd,&rset);
	    if (entry->vccs->fd >= fds) fds = entry->vccs->fd+1;
	}
	ret = select(fds,&rset,&cset,NULL,next_timer());
/*
 * Now here's something strange: < 0.32 needed the exception mask to be NULL
 * in order to work, due to a bug in atm_select. In 0.32, this has been fixed.
 * Also, 2.1 kernels use the poll mechanism and not select, so select is
 * emulated on top of poll. Now the funny bit is that, as soon as the exception
 * set is non-NULL, when a non-blocking connect finishes, select returns one
 * but has none if the possible bits set in either rset or cset. To make things
 * even stranger, no exception is actually found in sys_select, so this must be
 * some very odd side-effect ... The work-around for now is to simply pass NULL
 * for the exception mask (which is the right thing to do anyway, but it'd be
 * nice if doing a perfectly valid variation wouldn't blow up the system ...)
 */
#if 0
{
  int i;
  for (i = 0; i < sizeof(rset); i++)
   fprintf(stderr,"%02x:%02x ",((unsigned char *) &rset)[i],
    ((unsigned char *) &cset)[i]);
  fprintf(stderr,"\n");
}
#endif
	if (ret < 0) {
	    if (errno != EINTR) perror("select");
	}
	else {
	    diag(COMPONENT,DIAG_DEBUG,"----------");
	    gettimeofday(&now,NULL);
	    if (FD_ISSET(kernel,&rset)) recv_kernel();
	    if (incoming >= 0 && FD_ISSET(incoming,&rset)) accept_new();
	    for (itf = itfs; itf; itf = next_itf) {
		next_itf = itf->next;
		for (entry = itf->table; entry; entry = next_entry) {
		    next_entry = entry->next;
		    for (vcc = entry->vccs; vcc; vcc = next_vcc) {
			next_vcc = vcc->next;
			if (FD_ISSET(vcc->fd,&rset)) recv_vcc(vcc);
			else if (FD_ISSET(vcc->fd,&cset))
				complete_connect(vcc);
		    }
		}
	    }
	    for (entry = unknown_incoming; entry; entry = next_entry) {
		next_entry = entry->next;
		if (FD_ISSET(entry->vccs->fd,&rset)) recv_vcc(entry->vccs);
	    }
	    expire_timers();
	      /* expire timers after handling messages to make sure we don't
		 time out unnecessarily because of scheduling delays */
	}
	dump_all();
    }
}


void send_packet(int fd,void *data,int length)
{
    int wrote;

    if (debug) {
	int i;
	for (i = 0; i < length; i++)
	    printf("%02X ",((unsigned char *) data)[i]);
	printf("\n");
    }
    if ((wrote = write(fd,data,length)) == length) return;
    if (wrote < 0)
	diag(COMPONENT,DIAG_ERROR,"write: %s",strerror(errno));
    else diag(COMPONENT,DIAG_ERROR,"short write: %d < %d",wrote,length);
}


int ip_itf_info(int number,unsigned long *ip,unsigned long *netmask,int *mtu)
{
    struct ifreq req;
    unsigned char *p1,*p2;

    sprintf(req.ifr_ifrn.ifrn_name,"atm%d",number);
    if (ioctl(inet,SIOCGIFADDR,&req) < 0) {
	diag(COMPONENT,DIAG_ERROR,"ioctl SIOCGIFADDR: %s",strerror(errno));
	return -1;
    }
    *ip = ((struct sockaddr_in *) &req.ifr_ifru.ifru_addr)->sin_addr.s_addr;
    if (ioctl(inet,SIOCGIFNETMASK,&req) < 0) {
	diag(COMPONENT,DIAG_ERROR,"ioctl SIOCGIFNETMASK: %s",strerror(errno));
	return -1;
    }
    *netmask = ((struct sockaddr_in *) &req.ifr_ifru.ifru_netmask)->
      sin_addr.s_addr;
    if (ioctl(inet,SIOCGIFMTU,&req) < 0) {
	diag(COMPONENT,DIAG_ERROR,"ioctl SIOCGIFMTU: %s",strerror(errno));
	return -1;
    }
    *mtu = req.ifr_ifru.ifru_mtu;
    p1 = (unsigned char *) ip;
    p2 = (unsigned char *) netmask;
    diag(COMPONENT,DIAG_DEBUG,"ip %d.%d.%d.%d mask %d.%d.%d.%d mtu %d",
      p1[0],p1[1],p1[2],p1[3],p2[0],p2[1],p2[2],p2[3],*mtu);
    return 0;
}


int get_local(int fd,struct sockaddr_atmsvc *addr)
{
    int length,result;

    length = sizeof(struct sockaddr_atmsvc);
    result = getsockname(fd,(struct sockaddr *) addr,&length);
    if (result < 0)
	diag(COMPONENT,DIAG_ERROR,"getsockname: %s",strerror(errno));
    return result;
}
