static char IDudpclient[] = "@(#)udpclient 07/10/15";
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* gdbmtest [-/+options] port host kfmt rfmt
*
* This is a test program for the gdbmsrvr program.  It sends a  series
* of  SET  messages  to  gdbmsrvr,  and  then reads them back.  It has
* several levels of verbosity (debug levels) to  show  various  things
* about the results.
*
* It  takes  up to four arguments on its command line:  a hostname and
* port number, plus printf format strings  for  the  keys  and  record
* contents.   The  first  argument  that  starts  with a digit will be
* treated as the port number; the other three arguments must be in the
* order  shown.   The hostname defaults to "localhost" and the port to
* 9643.  The default key and record strings are shown in the -k and -r
* options below
*
* Options  may  start with '+' or '-' to enable/disable features; with
* the options for which this doesn't make sense, either may  be  used.
* An option letter may be upper or lower case.
*
* The options recognized are:
*
* -d<n><file>
*   The audit/debug/log/trace/verbose level and output file.  <n> is a
*   single digit, and defaults to 1. <file> is the output file, and if
*   omitted will be stderr.  Statistics are written  to  stdout.   The
*   values for <n> are:
*     0 no output at all.
*     1 statistics and fatal errors.
*     2 show the keys and data records.
*     3 show details of keys and records.
*     4 hex dumps of the messages.
*     5 and up are for debugging the code.
*
* -g
* +g
*   controls whether we do GETs; the default is +g.
*
* -k"KeyFmt"
*   defined a format for keys.  It is used as a  sprintf  format,  and
*   will have the message count addes as a parameter.  The default key
*   is "Key%d.".
*
* -r"KeyFmt"
*   defined a format for data records. It is used as a sprintf format,
*   and will also get the message count addes  as  a  parameter.   The
*   default key is "This is message %d.".
*
* -n<n>
*   number of messages:  write <n> messages; the default is 1.
*
* -s
* +s
*   controls whether we do SETs; the default is +s.
*
* -t<t>
*   timeout:  waits <t> seconds for replies; the default is 10.
*
* -w<s>
*   Wait time:  wait <s> seconds between messages; the default is 0.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "V.h"
#include "gdbm.h"
#include "gdbmsrvr.h"
#include "sys_socket.h"
#include "sys_in.h"
#include "sys_netdb.h"

Blk buf = {0};

char* kfmt = 0;
char* rfmt = 0;

char* en = "en";
char* dis = "dis";
char* dfl_host = "localhost";
char* hostname = 0;
char  ifmt[] = "%d";
int   reqs = 0;		/* Number of GET messages sent */
int   msgs = 1;		/* Number of messages to send */
int   rpls = 0;		/* Number of replies received */
TIMV  tmout = {1};	/* How long to wait in select() */
TIMV  t0={0}, t1={0}, t2={0}, t3={0}, t4={0}, t5={0}, tx={0};
char* portname = 0;
int   port = 0;
U32   now, then;	/* Timestamps */
U32   rmask = 0;	/* Select read mask */
int   getmout = 10;	/* Seconds to wait for supplies */
SKin  sndr = {0};	/* Address of sender */
int   sndl = 0;		/* Actual size of sndr */
int   wsleep = 0;	/* Seconds between writes */

Flag  setfl = 1;	/* Do SET loop */
Flag  getfl = 1;	/* Do GET loop */

datum key = {0};		/* One database key */
datum rec = {0};		/* One database record */

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
main(ac,av)
	int    ac;
	char** av;
{	int    r=0;	/* Result (exit status) */
	char*  p=0;
	int    c0, c1;
	int    a=0, i=0, m=0, msg=0, n=0, sock=0;
	U32    opkl=0, dlen=0, rqid=0, opcd=0, klen=0;
	SKin   addr;
	HOST*  hp=0;
	DBREC* rp=0;
	float  ival;

	ac = Vinit(ac,av);

	for (a=1; a<ac; a++) {
		V3 "Arg %2d: \"%s\"",a,av[a] D;
		c0 = av[a][0];
		Switch(c0) {
		case '-':
		case '+':
			V3 "Opt %2d: \"%s\"",a,av[a] D;
			c1 = av[a][1];
			Switch(c1) {
			case 'd':
			case 'D':
				V3 "Debug option: %s",Dspp(av[a]) D;
				Vopt(av[a]+2);
				break;
			case 'g':
			case 'G':
				getfl = (c0 == '+');
				V3 "GET operations %sabled.",(getfl?en:dis) D;
				break;
			case 'k':
			case 'K':
				kfmt = av[a] + 2;
				V3 "Key format: %s",Dspp(kfmt) D;
				break;
			case 'n':
			case 'N':
				if (sscanf(av[a]+2,ifmt,&msgs) < 1)
					++n;
				V3 "Writing %d messages.",msgs D;
				break;
			case 'r':
			case 'R':
				rfmt = av[a] + 2;
				V3 "Rec format: %s",Dspp(rfmt) D;
				break;
			case 's':
			case 'S':
				setfl = (c0 == '+');
				V3 "SET operations %sabled.",(setfl?en:dis) D;
				break;
			case 'w':
			case 'W':
				if (sscanf(av[a]+2,ifmt,&wsleep) < 1)
					++n;
				V3 "Wait %d seconds between messages.",wsleep D;
				break;
			case 't':
			case 'T':
				if (sscanf(av[a]+2,ifmt,&getmout) < 1)
					++n;
				V3 "Wait %d seconds for replies.",getmout D;
				break;
			default:
				V1 "Arg %2d: \"%s\" ignored.",a,av[a] D;
				break;
			}
			break;
		default:
			if (!portname && (sscanf(av[a],ifmt,&port) > 0)) {
				portname = av[a];
				V3 "Port: \"%s\"",portname D;
				V3 "port: %d.",port D;
			} else if (!hostname) {
				hostname = av[a];
				V3 "Host: \"%s\"",hostname D;
			} else if (!kfmt) {
				kfmt = av[a];
				V3 "Key format: \"%s\"",hostname D;
			} else if (!rfmt) {
				rfmt = av[a];
				V3 "Rec format: \"%s\"",rfmt D;
			} else {
				V1 "Arg %2d: \"%s\" ignored.",a,av[a] D;
			}
			break;
		}
	}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Take care of global defaults, for anything  that  wasn't  filled  in *
* from the command line or environment.                                *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
	if (!kfmt) kfmt = "Key%d.";
	if (!rfmt) rfmt = "This is message %d.";
	if (!buf.m) if (!MinBlkM(&buf,BUF,"buf")) Fail;
	if (!port) port = 9643;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Create socket on which to read.                                      *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
	V6 "before socket(AF_INET=%d, SOCK_DGRAM=%d, 0)",AF_INET,SOCK_DGRAM D;
	sock = Socket(AF_INET, SOCK_DGRAM, 0);
	if (sock < 0) {
		V1 "### Can't get datagram socket [Err %d=%s=%s]",Errinfo D;
		r = 2;
		Fail;
	}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Construct address of socket to send to.   Gethostbyname()  retuns  a *
* structure  including the network address of the specified host.  The *
* port number is taken from the command line.                          *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
	if (!hostname) {
		hostname = dfl_host;
		V3 "No host; using \"%s\"",hostname D;
	}
	hp = Gethostbyn(hostname);
	Bcopy(hp->h_addr,&(addr.sin_addr.s_addr),hp->h_length);
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	H5(&addr,sizeof(addr),"addr");

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Send the SET messages.
*/
	if (Vlvl > 0)
		Gettimeofday(&t1,0L);	/* t1 = before SETs */
	if (setfl) {
		V3 "Do %d SET messages ...",msgs D;
		for (m=1; m<=msgs; m++) {
			Sleep(wsleep);
			rqid++;
			rp = (DBREC*)buf.v;
			key.dptr = (CP)buf.v + sizeof(DBREC);
			Sprintf(key.dptr,kfmt,m);
			key.dsize = klen = Strlen(key.dptr);
			rec.dptr = (CP)buf.v + sizeof(DBREC) + klen;
			Sprintf(rec.dptr,rfmt,m);
			rec.dsize = dlen = Strlen(rec.dptr);
			H5(kfmt,strlen(kfmt),"kfmt");
			buf.l = sizeof(DBREC) + klen + dlen;
			Pack13(opkl,DBSET,klen);
			V5 "opkl=%08X dlen=%08lX rqid=%08lX",opkl,dlen,rqid D;
			V5 "key=[%08X,%d] rec=[%08X,%d]",key.dptr,key.dsize,rec.dptr,rec.dsize D;
			V3 "DBSET: key=%s",Dsps(key.dptr,key.dsize) D;
			V3 "DBSET: rec=%s",Dsps(rec.dptr,rec.dsize) D;
			rp->opkl = htonl(opkl);
			rp->dlen = htonl(dlen);
			rp->rqid = htonl(rqid);
			V3 "Send %d-byte reply to %s ...",buf.l,SymSockAddr(&sndr) D;
			V5 "Msg %2d: %s",m,DspSV(buf) D;
			H5(buf.v,buf.l,"buf");
			if (Sendto(sock, buf.v, buf.l, 0, &addr, sizeof(addr)) < 0) {
				V1 "Can't send message %d [Err %d=%s=%s]",m,Errinfo D;
				r = m;
				Fail;
			}
			if (Vlvl > 3) printf("SET: %s\n",DspSV(buf));
			if (Vlvl > 2) printf("SET: %d/%d/%d/%d %s %s\n"
					,DBSET,klen,dlen,rqid
					,Dsps(key.dptr,key.dsize)
					,Dsps(rec.dptr,rec.dsize));
			if (Vlvl > 1) printf("Set: %s %s\n"
					,Dsps(key.dptr,key.dsize)
					,Dsps(rec.dptr,rec.dsize));
		}
	}
	if (setfl && (Vlvl > 0)) {
		Gettimeofday(&t2,0L);	/* t2 is after GETs */
		tx.tv_sec  = t2.tv_sec  - t1.tv_sec;
		tx.tv_usec = t2.tv_usec - t1.tv_usec;
		while (tx.tv_usec < 0) {
			tx.tv_usec += 1000000;
			tx.tv_sec  -= 1;
		}
		ival = (float)tx.tv_sec + (tx.tv_usec / 1000000.0);
		printf("%8.3f SETs/sec (%d writes in %.3f sec)\n"
			,msgs/ival,msgs,ival);
	}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Send off the GET requests, and look for replies.
*/
	if (!getfl) {
		V3 "GET operations skipped." D;
		Done;
	}
	V3 "Wait %d seconds for replies ...",getmout D;
	msg = 0;
	rp = (DBREC*)buf.v;
	key.dptr = (CP)buf.v + sizeof(DBREC);
	Gettimeofday(&t3,0L);
	t4 = t3;
	now = then = t3.tv_sec;	/* Start of timeout */
loop:
	if (rpls >= msgs) {
		V3 "Got %d replies for %d requests.",rpls,msgs D;
		Done;
	}
	rmask = 1 << sock;
	Gettimeofday(&tx,0L);	/* tx is start of loop */
	now = tx.tv_sec;
	if ((getmout - (now - then)) < 0) {	/* Has it been too long since last reply? */
		V3 "Out of time." D;
		if (Vlvl > 0)
			printf("Timeout: No reply in %d sec.\n",now-then);
		Done;
	}
	tmout.tv_sec = 1;
	Sleep(wsleep);
	++msg;
	++rqid;
	V3 "Send GET message %d of %d ...",msg,msgs D;
	Sprintf(key.dptr,kfmt,msg);
	key.dsize = Strlen(key.dptr);
	buf.l = sizeof(DBREC) + key.dsize;
	Pack13(opkl,DBGET,key.dsize);
	V5 "opkl=%08X rqid=%08lX",opkl,rqid D;
	V5 "key=[%08X,%d] rec=[%08X,%d]",key.dptr,key.dsize,rec.dptr,rec.dsize D;
	V3 "DBGET: key=%s",Dsps(key.dptr,key.dsize) D;
	V3 "DBGET: rec=%s",Dsps(rec.dptr,rec.dsize) D;
	rp->opkl = htonl(opkl);
	rp->dlen = htonl(0);
	rp->rqid = htonl(rqid);
	V3 "Send %d-byte reply to %s ...",buf.l,SymSockAddr(&sndr) D;
	V5 "Msg %2d: %s",msg,DspSV(buf) D;
	H5(buf.v,buf.l,"buf");
	if (Sendto(sock, buf.v, buf.l, 0, &addr, sizeof(addr)) < 0) {
		V1 "Can't send message %d [Err %d=%s=%s]",msg,Errinfo D;
		r = msg;
		Fail;
	}
	++reqs;
	if (Vlvl > 3) printf("GET: %s\n",DspSV(buf));
	if (Vlvl > 2) printf("GET: %d/%d/%d/%d %s\n"
			,DBGET,key.dsize,0,rqid
			,Dsps(key.dptr,key.dsize));
	if ((i = Select(sock+1, &rmask,0,0,&tmout)) > 0) {
		V3 "Got input, rmask=%08X",rmask D;
		sndl = sizeof(sndr);
		if ((n = Recvfrom(sock, buf.v, BUF, 0, &sndr, &sndl)) > 0) {
			++rpls;
			t4 = t5;
			buf.l = n;
			if (Vlvl > 3) printf("GOT: %s\n",DspSV(buf));
			V3 "Recv UDP %d bytes file %d addr %s %s\n",
				n, sock,
				SymSockAddr(&sndr),
				DspSV(buf) D;
			buf.l = n;
			then = tx.tv_sec;		/* Reset the timeout limit */
			H5(buf.v,buf.l,"buf");
			rp = (DBREC*)buf.v;
			opkl = ntohl(rp->opkl);
			rec.dsize =
			dlen = ntohl(rp->dlen);
			rqid = ntohl(rp->rqid);
			V5 "opkl=%08X dlen=%08lX rqid=%08lX",opkl,dlen,rqid D;
			key.dsize = Unpack13(opkl,opcd,klen);
			V3 "Opcode=%d keylen=%d datalen=%d requestid=%d.",opcd,klen,dlen,rqid D;
			m = sizeof(DBREC) + klen + dlen;
			if (m != buf.l) {
				V3 "From %s %d+%d+%d=%d != msg size %d.",SymSockAddr(&sndr),sizeof(DBREC),klen,dlen,m,buf.l D;
				Loop;		/* Drop it */
			}
			rec.dptr = (CP)buf.v + sizeof(DBREC) + klen;
			if (Vlvl > 2) printf("GOT: %d/%d/%d/%d %s %s\n"
					,DBGET,klen,dlen,rqid
					,Dsps(key.dptr,key.dsize)
					,Dsps(rec.dptr,rec.dsize));
			Switch(opcd) {
			case DBGET:
				V3 "DBGET: treat as DBRPL ..." D;
			case DBRPL:
				V3 "DBRPL: Reply" D;
				if (buf.m < m) if (!MinBlkM(&buf,m,"buf")) Fail;
				key.dptr = (CP)buf.v + sizeof(DBREC);
				rec.dptr = (CP)buf.v + sizeof(DBREC) + klen;
				key.dsize = klen;
				rec.dsize = dlen;
				V4 "DB key: %s",Dsps(key.dptr,key.dsize) D;
				V4 "DB rec: %s",Dsps(rec.dptr,rec.dsize) D;
				V2 "Got: %s %s",Dsps(key.dptr,key.dsize),Dsps(rec.dptr,rec.dsize) D;
				if (Vlvl > 1) printf("Got: %s %s\n"
						,Dsps(key.dptr,key.dsize)
						,Dsps(rec.dptr,rec.dsize));
				break;
			case DBSET:
				V3 "DBSET: ignored by %s.",pname D;
				break;
			case DBDEL:
				V3 "DBDEL: ignored by %s.",pname D;
				break;
			case DBACK:
				V3 "DBACK: ignored by %s.",pname D;
				break;
			case DBSYN:
				V3 "DBSYN: ignored by %s.",pname D;
				break;
			default:
				V3 "From %s opcode %02X=%d unknown.",SymSockAddr(&sndr),opcd,opcd D;
				Loop;		/* Drop it */
			}

			Loop;
		}
	}
	if (i == 0) {
		V3 "Timeout." D;
		Loop;
	}
	V3 "Can't read from socket [Err %d=%s=%s]",Errinfo D;
fail:
done:
	if (getfl && (Vlvl > 0)) {
		Gettimeofday(&t5,0L);	/* t5 is after GETs */
		tx.tv_sec  = t5.tv_sec  - t3.tv_sec;
		tx.tv_usec = t5.tv_usec - t3.tv_usec;
		while (tx.tv_usec < 0) {
			tx.tv_usec += 1000000;
			tx.tv_sec  -= 1;
		}
		ival = (float)tx.tv_sec + (tx.tv_usec / 1000000.0);
		printf("%8.3f GETs/sec (%d replies to %d requests in %.3f sec)\n",
			rpls/ival,rpls,reqs,ival);
	}
	Close(sock);	/* For debug messages */
	Exit(r);
}
usage()
{
	printf("Usage: %s [-/+options] [host] [port]\n", pname);
}

