/* aqtest.c - Simplistic Arequipa tester */

/* Written 1997 by Werner Almesberger, EPFL-LRC */
/* Based on work by Mehdi Tazi */


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <atm.h>
#include <arequipa.h>
#include <linux/atm.h>


#define DEFAULT_PORT 1997
#define MSG "just testing"
#define BUF_SIZE 100
#define MAX_QOS 16


static int verbose = 0;


static void say(const char *msg)
{
    if (!verbose) return;
    printf("%s\n",msg);
    fflush(stdout);
}


static void fail(const char *msg)
{
    perror(msg);
    exit(1);
}


static void mismatch(const char *msg,int size)
{
    fprintf(stderr,"%s %d bytes instead of %d\n",msg,size,strlen(MSG)+1);
    exit(1);
}


static void usage(const char *name)
{
    fprintf(stderr,"usage: %s [-a atm_host[/qos[/qos ... ]]] [-p port] [-v] "
      "[host] sequence\n",name);
    fprintf(stderr,"sequence consists of the following characters:\n");
    fprintf(stderr,"  a accept (and close listening socket)\n");
    fprintf(stderr,"  b bind\n");
    fprintf(stderr,"  c close\n");
    fprintf(stderr,"  d delay (1 second)\n");
    fprintf(stderr,"  e arequipa_expect\n");
    fprintf(stderr,"  f arequipa_close\n");
    fprintf(stderr,"  h hang (loop forever)\n");
    fprintf(stderr,"  l listen\n");
    fprintf(stderr,"  m modify\n");
    fprintf(stderr,"  o connect\n");
    fprintf(stderr,"  p arequipa_preset\n");
    fprintf(stderr,"  r read\n");
    fprintf(stderr,"  s socket\n");
    fprintf(stderr,"  w write\n");
    exit(1);
}


int main(int argc,char **argv)
{
    struct atm_qos qos[MAX_QOS];
    struct sockaddr_atmsvc addr_atm;
    struct sockaddr_in addr_in;
    const char *port;
    char *here,*end,*walk;
    int opt_a,c,s;
    int qos_avail,curr_qos;

    opt_a = 0;
    port = NULL;
    qos_avail = 0;
    while ((c = getopt(argc,argv,"a:p:v")) != EOF)
	switch (c) {
	    case 'a':
		memset(&addr_atm,0,sizeof(addr_atm));
		here = strchr(optarg,'/');
		if (!(here = strchr(optarg,'/'))) {
		    memset(&qos,0,sizeof(struct atm_qos));
		    qos->txtp.traffic_class = qos->rxtp.traffic_class = ATM_UBR;
		    qos_avail = 1;
		}
		else {
		    qos_avail = 0;
		    *here++ = 0;
		    for (here = strtok(here,"/"); here;
		      here = strtok(NULL,"/")) {
			if (qos_avail == MAX_QOS) {
			    fprintf(stderr,"only %d QOS specs configured\n",
			      MAX_QOS);
			    return 1;
			}
			if (text2qos(here,qos+qos_avail,0) < 0) {
			    fprintf(stderr,"invalid QOS specification: %s\n",
			      here);
			    return 1;
			}
			qos_avail++;
		    }
		}
		if (text2atm(optarg,(struct sockaddr *) &addr_atm,
		  sizeof(addr_atm),T2A_SVC | T2A_NAME) < 0) {
		    fprintf(stderr,"invalid ATM address: %s\n",optarg);
		    return 1;
		}
		opt_a = 1;
		break;
	    case 'p':
		port = optarg;
		break;
	    case 'v':
		verbose = 1;
		break;
	    default:
		usage(argv[0]);
	}
    addr_in.sin_family = AF_INET;
    if (argc == optind+1) addr_in.sin_addr.s_addr = htonl(INADDR_ANY);
    else {
	if (argc != optind+2) usage(argv[0]);
	if ((int) (addr_in.sin_addr.s_addr = inet_addr(argv[optind])) == -1) {
	    struct hostent *he;

	    if (!(he = gethostbyname(argv[optind]))) {
		herror(argv[optind]);
		return 1;
	    }
	    memcpy(&addr_in.sin_addr,he->h_addr,he->h_length);
	}
    }
    if (!port) addr_in.sin_port = htons(DEFAULT_PORT);
    else {
	addr_in.sin_port = htons(strtoul(port,&end,10));
	if (*end) {
	    struct servent *se;

	    if (!(se = getservbyname(port,"tcp"))) {
		fprintf(stderr,"invalid port: %s\n",port);
		return 1;
	    }
	    addr_in.sin_port = se->s_port;
	}
    }
    curr_qos = 0;
    if (*argv[argc-1] != 's') {
	fprintf(stderr,"sequence must start with \"s\"\n");
	return 1;
    }
    s = -1;
    for (walk = argv[argc-1]; *walk; walk++)
	switch (*walk) {
	    case 'a':
		{
		    int s2;

		    say("accept");
		    if ((s2 = accept(s,NULL,NULL)) < 0) fail("accept");
		    if (close(s) < 0) fail("close after accept");
		    s = s2;
		}
		break;
	    case 'b':
		say("bind");
		if (bind(s,(struct sockaddr *) &addr_in,sizeof(addr_in)) < 0)
		    fail("bind");
		break;
	    case 'c':
		say("close");
		if (close(s) < 0) fail("close");
		break;
	    case 'd':
		say("delay (sleep)");
		sleep(1);
		break;
	    case 'e':
		say("arequipa_expect");
		if (arequipa_expect(s,1) < 0) fail("arequipa_expect");
		break;
	    case 'f':
		say("arequipa_close");
		if (arequipa_close(s) < 0) fail("arequipa_close");
		break;
	    case 'h':
		say("hanging");
		while (1) sleep(1);
	    case 'l':
		say("listen");
		if (listen(s,1) < 0) fail("listen");
		break;
	    case 'm':
		if (curr_qos == qos_avail) {
		    fprintf(stderr,"no QOS spec left for "
		      "arequipa_renegotiate\n");
		    return 1;
		}
		say("modify (renegotiate)");
		if (arequipa_renegotiate(s,qos+curr_qos++) < 0)
		    fail("arequipa_renegotiate");
		break;
	    case 'o':
		say("open (connect)");
		if (connect(s,(struct sockaddr *) &addr_in,sizeof(addr_in)) < 0)
		    fail("open (connect)");
		break;
	    case 'p':
		if (!opt_a) {
		    fprintf(stderr,"must specify ATM address for "
		      "arequipa_preset\n");
		    return 1;
		}
		if (curr_qos == qos_avail) {
		    fprintf(stderr,"no QOS spec left for arequipa_preset\n");
		    return 1;
		}
		say("arequipa_preset");
		if (arequipa_preset(s,&addr_atm,qos+curr_qos++) < 0)
		    fail("arequipa_preset");
		break;
	    case 'r':
		{
		    char buffer[BUF_SIZE];
		    int size;

		    say("read");
		    size = read(s,buffer,strlen(MSG)+1);
		    if (size < 0) fail("read");
		    if (size != strlen(MSG)+1) mismatch("read",size);
		    if (strcmp(MSG,buffer)) {
			fprintf(stderr,"received string doesn't match sent "
			  "string\n");
			return 1;
		    }
		}
		break;
	    case 's':
		say("socket");
		if ((s = socket(PF_INET,SOCK_STREAM,0)) < 0) fail("socket");
		break;
	    case 'w':
		{
		    int size;

		    say("write");
		    size = write(s,MSG,strlen(MSG)+1);
		    if (size < 0) fail("write");
		    if (size != strlen(MSG)+1) mismatch("wrote",size);
		}
		break;
	    default:
		usage(argv[0]);
	}
    return 0;
}
