/*
Written by Matt Wright and Adrian Freed, The Center for New Music and
Audio Technologies, University of California, Berkeley.  Copyright (c)
1992,93,94,95,96,97,98,99,2000,01,02,03,04 The Regents of the University of
California (Regents).

Permission to use, copy, modify, distribute, and distribute modified versions
of this software and its documentation without fee and without a signed
licensing agreement, is hereby granted, provided that the above copyright
notice, this paragraph and the following two paragraphs appear in all copies,
modifications, and distributions.

IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING
OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF REGENTS HAS
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED
HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO OBLIGATION TO PROVIDE
MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.


The OSC webpage is http://cnmat.cnmat.berkeley.edu/OpenSoundControl
*/


  /* 

     dumpOSC.c
	server that displays OpenSoundControl messages sent to it
	for debugging client udp and UNIX protocol

     by Matt Wright, 6/3/97
       modified from dumpSC.c, by Matt Wright and Adrian Freed

     version 0.2: Added "-silent" option a.k.a. "-quiet"

     version 0.3: Incorporated patches from Nicola Bernardini to make
       things Linux-friendly.  Also added ntohl() in the right places
       to support little-endian architectures.
 
     CNMAT OSX compile changes not included (20040820) jdl

	compile:
		cc -o dumpOSC dumpOSC.c

	to-do:

	    More robustness in saying exactly what's wrong with ill-formed
	    messages.  (If they don't make sense, show exactly what was
	    received.)

	    Time-based features: print time-received for each packet

	    Clean up to separate OSC parsing code from socket/select stuff

*/


#ifdef UNIX 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <rpc/rpc.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/times.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <pwd.h>
#include <signal.h>
#include <grp.h>
#include <sys/file.h>
#ifndef __APPLE__
  #include <sys/prctl.h>
  #include <bits/sigset.h>
#endif

#ifdef NEED_SCHEDCTL_AND_LOCK
#include <sys/schedctl.h>
#include <sys/lock.h>
#endif


char *htm_error_string;
typedef int Boolean;
typedef void *OBJ;

typedef struct ClientAddressStruct {
        struct sockaddr_in  cl_addr;
        int clilen;
        int sockfd;
} *ClientAddr;

Boolean ShowBytes = FALSE;
Boolean Silent = FALSE;

/* Declarations */
static int unixinitudp(int chan);
static int initudp(int chan);
static void closeudp(int sockfd);
Boolean ClientReply(int packetsize, void *packet, int socketfd, 
	void *clientaddresspointer, int clientaddressbufferlength);
void sgi_CleanExit(void);
Boolean sgi_HaveToQuit(void);
int RegisterPollingDevice(int fd, void (*callbackfunction)(int , void *), void *dummy);
#ifndef __APPLE__
static void catch_sigint();
#endif
static int Synthmessage(char *m, int n, void *clientdesc, int clientdesclength, int fd) ;
void ParseOSCPacket(char *buf, int n, ClientAddr returnAddr);
static void Smessage(char *address, void *v, int n, ClientAddr returnAddr);
static void PrintTypeTaggedArgs(void *v, int n);
static void PrintHeuristicallyTypeGuessedArgs(void *v, int n, int skipComma);
char *DataAfterAlignedString(char *string, char *boundary) ;
Boolean IsNiceString(char *string, char *boundary) ;
void complain(char *s, ...);


#define UNIXDG_PATH "/tmp/htm"
#define UNIXDG_TMP "/tmp/htm.XXXXXX"
static int unixinitudp(int chan)
{
	struct sockaddr_un serv_addr;
	int  sockfd;
	
	if((sockfd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0)
			return sockfd;
	
	memset((char *)&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sun_family = AF_UNIX;
	strcpy(serv_addr.sun_path, UNIXDG_PATH);
	sprintf(serv_addr.sun_path+strlen(serv_addr.sun_path), "%d", chan);
	 unlink(serv_addr.sun_path);
	if(bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr.sun_family)+strlen(serv_addr.sun_path)) < 0)
	{
		perror("unable to  bind\n");
		return -1;
	}

	fcntl(sockfd, F_SETFL, FNDELAY); 
	return sockfd;
}

static int initudp(int chan)
{
	struct sockaddr_in serv_addr;
	int  sockfd;
	
	if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
			return sockfd;
	memset((char *)&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_addr.sin_port = htons(chan);
	
	if(bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
	{
		perror("unable to  bind\n");
		return -1;
	}

	fcntl(sockfd, F_SETFL, FNDELAY); 
	return sockfd;
}

static void closeudp(int sockfd) {
   close(sockfd);
}

static Boolean catchupflag=FALSE;
Boolean ClientReply(int packetsize, void *packet, int socketfd, 
	void *clientaddresspointer, int clientaddressbufferlength)
{
	if(!clientaddresspointer) return FALSE;
	catchupflag= TRUE;
	return packetsize==sendto(socketfd, packet, packetsize, 0, clientaddresspointer, clientaddressbufferlength);
}

static Boolean exitflag= FALSE;
void sgi_CleanExit(void) {
	exitflag = TRUE;
}

Boolean sgi_HaveToQuit(void) {
	return exitflag;
}


/* file descriptor poll table */
static int npolldevs =0;
typedef struct polldev
{
	int fd;
	void (*callbackfunction)(int , void *);
	void *dummy;
} polldev;
#define TABMAX 8
static polldev polldevs[TABMAX];


/*      Register a device (referred to by a file descriptor that the caller
	should have already successfully obtained from a system call) to be
	polled as real-time constraints allowed.
        
        When a select(2) call indicates activity on the file descriptor, the
	callback function is called with the file descripter as first
	argument and the given dummy argument (presumably a pointer to the
	instance variables associated with the device).
*/
int RegisterPollingDevice(int fd, void (*callbackfunction)(int , void *), void *dummy)
{
	if(npolldevs<TABMAX)
	{
		polldevs[npolldevs].fd = fd;
		polldevs[npolldevs].callbackfunction = callbackfunction;
		polldevs[npolldevs].dummy = dummy;
	}
	else return -1;
	return npolldevs++;
}

static int caught_sigint;

#ifndef __APPLE__
static void catch_sigint()  {
   caught_sigint = 1;
}
#endif
static int sockfd, usockfd;



static int Synthmessage(char *m, int n, void *clientdesc, int clientdesclength, int fd)  {
    struct ClientAddressStruct ras;
    ClientAddr ra = &ras;

    catchupflag= FALSE;

    ras.cl_addr = *((struct sockaddr_in *) clientdesc);
    ras.clilen = clientdesclength;
    ras.sockfd = fd;

    if (ShowBytes) {
	int i;
	printf("%d byte message:\n", n);
	for (i = 0; i < n; ++i) {
	    printf(" %x (%c)\t", m[i], m[i]);
	    if (i%4 == 3) printf("\n");
	}
	printf("\n");
    }

    ParseOSCPacket(m, n, ra);
    return catchupflag;
}

void PrintClientAddr(ClientAddr CA) {
    unsigned long addr = CA->cl_addr.sin_addr.s_addr;
    printf("Client address %p:\n", CA);
    printf("  clilen %d, sockfd %d\n", CA->clilen, CA->sockfd);
    printf("  sin_family %d, sin_port %d\n", CA->cl_addr.sin_family,
	   CA->cl_addr.sin_port);
    printf("  address: (%x) %s\n", addr,  inet_ntoa(CA->cl_addr.sin_addr));

    printf("  sin_zero = \"%c%c%c%c%c%c%c%c\"\n", 
	   CA->cl_addr.sin_zero[0],
	   CA->cl_addr.sin_zero[1],
	   CA->cl_addr.sin_zero[2],
	   CA->cl_addr.sin_zero[3],
	   CA->cl_addr.sin_zero[4],
	   CA->cl_addr.sin_zero[5],
	   CA->cl_addr.sin_zero[6],
	   CA->cl_addr.sin_zero[7]);

    printf("\n");
}


void ParseOSCPacket(char *buf, int n, ClientAddr returnAddr) {
    int size, messageLen, i;
    char *messageName;
    char *args;

#ifdef PRINTADDRS
    PrintClientAddr(returnAddr);
#endif


    if ((n%4) != 0) {
	complain("SynthControl packet size (%d) not a multiple of 4 bytes: dropping",
		 n);
	return;
    }

    if ((n >= 8) && (strncmp(buf, "#bundle", 8) == 0)) {
	/* This is a bundle message. */

	if (n < 16) {
	    complain("Bundle message too small (%d bytes) for time tag", n);
	    return;
	}

	/* Print the time tag */
	printf("[ %lx%08lx\n", ntohl(*((unsigned long *)(buf+8))),
	       ntohl(*((unsigned long *)(buf+12))));
	/* Note: if we wanted to actually use the time tag as a little-endian
	   64-bit int, we'd have to word-swap the two 32-bit halves of it */

	i = 16; /* Skip "#group\0" and time tag */
	while(i<n) {
	    size = ntohl(*((int *) (buf + i)));
	    if ((size % 4) != 0) {
		complain("Bad size count %d in bundle (not a multiple of 4)", size);
		return;
	    }
	    if ((size + i + 4) > n) {
		complain("Bad size count %d in bundle (only %d bytes left in entire bundle)",
			 size, n-i-4);
		return;	
	    }
	    
	    /* Recursively handle element of bundle */
	    ParseOSCPacket(buf+i+4, size, returnAddr);
	    i += 4 + size;
	}
	if (i != n) {
	    complain("This can't happen");
	}
	printf("]\n");
    } else {
	/* This is not a bundle message */

	messageName = buf;
	args = DataAfterAlignedString(messageName, buf+n);
	if (args == 0) {
	    complain("Bad message name string: %s\nDropping entire message.\n",
		     htm_error_string);
	    return;
	}
	messageLen = args-messageName;	    
	Smessage(messageName, (void *)args, n-messageLen, returnAddr);
    }
}

#define SMALLEST_POSITIVE_FLOAT 0.000001f

static void Smessage(char *address, void *v, int n, ClientAddr returnAddr) {
    char *chars = v;

    printf("%s ", address);

    if (n != 0) {
	if (chars[0] == ',') {
	    if (chars[1] != ',') {
		/* This message begins with a type-tag string */
		PrintTypeTaggedArgs(v, n);
	    } else {
		/* Double comma means an escaped real comma, not a type string */
		PrintHeuristicallyTypeGuessedArgs(v, n, 1);
	    }
	} else {
	    PrintHeuristicallyTypeGuessedArgs(v, n, 0);
	}
    }

    printf("\n");
    fflush(stdout);	/* Added for Sami 5/21/98 */
}

static void PrintTypeTaggedArgs(void *v, int n) { 
    char *typeTags, *thisType;
    char *p;

    typeTags = v;

    if (!IsNiceString(typeTags, typeTags+n)) {
	/* No null-termination, so maybe it wasn't a type tag
	   string after all */
	PrintHeuristicallyTypeGuessedArgs(v, n, 0);
	return;
    }

    p = DataAfterAlignedString(typeTags, typeTags+n);


    for (thisType = typeTags + 1; *thisType != 0; ++thisType) {
	switch (*thisType) {
	    case 'i': case 'r': case 'm': case 'c':
	    printf("%d ", ntohl(*((int *) p)));
	    p += 4;
	    break;

	    case 'f': {
		int i = ntohl(*((int *) p));
		float *floatp = ((float *) (&i));
		printf("%f ", *floatp);
		p += 4;
	    }
	    break;

	    case 'h': case 't':
	    printf("[A 64-bit int] ");
	    p += 8;
	    break;

	    case 'd':
	    printf("[A 64-bit float] ");
	    p += 8;
	    break;

	    case 's': case 'S':
	    if (!IsNiceString(p, typeTags+n)) {
		printf("Type tag said this arg is a string but it's not!\n");
		return;
	    } else {
		printf("\"%s\" ", p);
		p = DataAfterAlignedString(p, typeTags+n);
	    }
	    break;

	    case 'T': printf("[True] "); break;
	    case 'F': printf("[False] "); break;
	    case 'N': printf("[Nil]"); break;
	    case 'I': printf("[Infinitum]"); break;

	    default:
	    printf("[Unrecognized type tag %c]", *thisType);
	    return;
	}
    }
}

static void PrintHeuristicallyTypeGuessedArgs(void *v, int n, int skipComma) {
    int i, thisi;
    float thisf;
    int *ints;
    char *chars;
    char *string, *nextString;
	

    /* Go through the arguments 32 bits at a time */
    ints = v;
    chars = v;

    for (i = 0; i<n/4; ) {
	string = &chars[i*4];
	thisi = ntohl(ints[i]);
	/* Reinterpret the (potentially byte-reversed) thisi as a float */
	thisf = *(((float *) (&thisi)));

	if  (thisi >= -1000 && thisi <= 1000000) {
	    printf("%d ", thisi);
	    i++;
	} else if (thisf >= -1000.f && thisf <= 1000000.f &&
		   (thisf <=0.0f || thisf >= SMALLEST_POSITIVE_FLOAT)) {
	    printf("%f ",  thisf);
	    i++;
	} else if (IsNiceString(string, chars+n)) {
	    nextString = DataAfterAlignedString(string, chars+n);
	    printf("\"%s\" ", (i == 0 && skipComma) ? string +1 : string);
	    i += (nextString-string) / 4;
	} else {
	    printf("0x%x ", ints[i]);
	    i++;
	}
    }
}


#define STRING_ALIGN_PAD 4

char *DataAfterAlignedString(char *string, char *boundary) 
{
    /* The argument is a block of data beginning with a string.  The
       string has (presumably) been padded with extra null characters
       so that the overall length is a multiple of STRING_ALIGN_PAD
       bytes.  Return a pointer to the next byte after the null
       byte(s).  The boundary argument points to the character after
       the last valid character in the buffer---if the string hasn't
       ended by there, something's wrong.

       If the data looks wrong, return 0, and set htm_error_string */

    int i;

    if ((boundary - string) %4 != 0) {
	fprintf(stderr, "Internal error: DataAfterAlignedString: bad boundary\n");
	return 0;
    }

    for (i = 0; string[i] != '\0'; i++) {
	if (string + i >= boundary) {
	    htm_error_string = "DataAfterAlignedString: Unreasonably long string";
	    return 0;
	}
    }

    /* Now string[i] is the first null character */
    i++;

    for (; (i % STRING_ALIGN_PAD) != 0; i++) {
	if (string + i >= boundary) {
	    htm_error_string = "DataAfterAlignedString: Unreasonably long string";
	    return 0;
	}
	if (string[i] != '\0') {
	    htm_error_string = "DataAfterAlignedString: Incorrectly padded string.";
	    return 0;
	}
    }

    return string+i;
}

Boolean IsNiceString(char *string, char *boundary) 
{
    /* Arguments same as DataAfterAlignedString().  Is the given "string"
       really a string?  I.e., is it a sequence of isprint() characters
       terminated with 1-4 null characters to align on a 4-byte boundary? */

    int i;

    if ((boundary - string) %4 != 0) {
	fprintf(stderr, "Internal error: IsNiceString: bad boundary\n");
	return 0;
    }

    for (i = 0; string[i] != '\0'; i++) {
	if (!isprint(string[i])) return FALSE;
	if (string + i >= boundary) return FALSE;
    }

    /* If we made it this far, it's a null-terminated sequence of printing characters 
       in the given boundary.  Now we just make sure it's null padded... */

    /* Now string[i] is the first null character */
    i++;
    for (; (i % STRING_ALIGN_PAD) != 0; i++) {
	if (string[i] != '\0') return FALSE;
    }

    return TRUE;
}







#define MAXMESG 32768
static char mbuf[MAXMESG];

int main(int argc, char **argv) {
    int udp_port;		/* port to receive parameter updates from */
		
	struct sockaddr_in  cl_addr;
	int clilen,maxclilen=sizeof(cl_addr);
	struct sockaddr_un  ucl_addr;
	int uclilen,umaxclilen=sizeof(ucl_addr);
	int i,n;
	
		
	clilen = maxclilen;
	uclilen = umaxclilen;

    udp_port = -1;
    for (i=1; i < argc; ++i) {
	if (strcmp(argv[i], "-showbytes") == 0) {
	    ShowBytes = TRUE;
	} else if (strcmp(argv[i], "-silent") == 0 ||
		   strcmp(argv[i], "-quiet") == 0) {
	    Silent = TRUE;
	} else if (udp_port != -1) {
	    goto usageError;
	} else {
	    udp_port = atoi(argv[i]);
	    if (udp_port == 0) {
		goto usageError;
	    }
	}
    }

    if (udp_port == -1) {
	usageError:
	    fprintf(stderr, "Usage\n\tdumpOSC portno [-showbytes] [-quiet]\n\t(responds to udp and UNIX packets on that port no)\n");
	    exit(1);
    }


	n = recvfrom(0, mbuf, MAXMESG, 0, &cl_addr, &clilen);
	if(n>0)
	{
		sockfd = 0;
		udp_port = -1;
		Synthmessage(mbuf, n, &cl_addr, clilen,sockfd) ;
	}
	else
	{	sockfd=initudp(udp_port);
		usockfd=unixinitudp(udp_port);
	}

    if (!Silent) {
	printf("dumpOSC version 0.2 (6/18/97 Matt Wright). Unix/UDP Port %d \n", udp_port);	
	printf("Copyright (c) 1992,1996,1997 Regents of the University of California.\n");
    }
	if(sockfd>=0 && usockfd>=0)
	{
		fd_set read_fds, write_fds;
		int nfds;
#define max(a,b) (((a) > (b)) ? (a) : (b))
		nfds = max(sockfd, usockfd)+ 1;
		{
			int j;
			for(j=0;j<npolldevs;++j)
				if(polldevs[j].fd>=nfds)
				{
						nfds = polldevs[j].fd+1;
/*
printf("polldev %d\n", polldevs[j].fd);
*/
				}
		}
/*
		printf("nfds %d\n", nfds);
*/
		caught_sigint = 0;
#ifndef __APPLE__
   		sigset(SIGINT, catch_sigint);       /* set sig handler       */
#endif	
		while(!caught_sigint)
		{
			
			int r;
 	
		back:	

			FD_ZERO(&read_fds);                /* clear read_fds        */
			FD_ZERO(&write_fds);               /* clear write_fds        */
			FD_SET(sockfd, &read_fds); 
			FD_SET(usockfd, &read_fds); 
		       	{
		        	int j;
		        	
		        	for(j=0;j<npolldevs;++j)
		        		FD_SET(polldevs[j].fd, &read_fds);
		        }
 	      
		        r = select(nfds, &read_fds, &write_fds, (fd_set *)0, 
		                        (struct timeval *)0);
		        if (r < 0)  /* select reported an error */
			    goto out;
		        {
			    int j;
			    
			    for(j=0;j<npolldevs;++j)
				    if(FD_ISSET(polldevs[j].fd, &read_fds))
				    (*(polldevs[j].callbackfunction))(polldevs[j].fd,polldevs[j].dummy );
		        }
		        if(FD_ISSET(sockfd, &read_fds))
			{
				clilen = maxclilen;
				while( (n = recvfrom(sockfd, mbuf, MAXMESG, 0, &cl_addr, &clilen)) >0) 
				{
				  int r;
				  /* printf("received UDP packet of length %d\n",  n); */
				  r = Synthmessage(mbuf, n, &cl_addr, clilen, sockfd) ;

				  if( sgi_HaveToQuit()) goto out;
				  if(r>0) goto back;
				  clilen = maxclilen;
				}
			}
		        if(FD_ISSET(usockfd, &read_fds))
			{
				uclilen = umaxclilen;
				while( (n = recvfrom(usockfd, mbuf, MAXMESG, 0, &ucl_addr, &uclilen)) >0) 
				{
				  int r;
				  /* printf("received UNIX packet of length %d\n",  n); */

				  r=Synthmessage(mbuf, n, &ucl_addr, uclilen,usockfd) ;

				  if( sgi_HaveToQuit()) goto out;
				  if(r>0) goto back;
				  uclilen = umaxclilen;
				}
			}
		} /* End of while(!caught_sigint) */
	   closeudp(sockfd);
		
out: ;
	}
	else
		perror("initudp");
	
	return 0;
}


#include <stdarg.h>
void complain(char *s, ...) {
    va_list ap;
    va_start(ap, s);
    fprintf(stderr, "*** ERROR: ");
    vfprintf(stderr, s, ap);
    fprintf(stderr, "\n");
    va_end(ap);
}

#endif /* __sgi or LINUX */
