/* Copyright (C) 2005 by Howard Chu.
 * http://www.highlandsun.com/hyc/
 *
 * You may distribute this program under the terms of the
 * GNU General Public License version 2.
 *
 * This program receives caller ID info that was broadcast
 * over UDP and displays it to stdout. It decodes both
 * SDMF (used for caller ID number-only service) as well as
 * MDMF (used for name-and-number service). The checksum
 * at the end is always ignored; presumably the data arrived
 * intact at the sender and it didn't get garbled by the IP
 * network.
 *
 * This program has been compiled and run under Linux and
 * Windows (using MSVC as well as MSYS/GCC). It's fairly
 * generic code. You could easily extend it to feed events
 * to some other system, to initiate complex reactions to
 * incoming call data, but all I needed was a text display.
 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#ifdef _WIN32
#include <winsock2.h>
#define USE_WINSOCK
#define	sock_err(msg)	fprintf(stderr,"%s error %d\n",msg,WSAGetLastError())
#else
#include <sys/socket.h>
#include <netinet/in.h>
#define	sock_err(msg)	perror(msg)
#endif
#include <string.h>

#define	DEFPORT	10288

int udp;
unsigned short port = DEFPORT;
struct sockaddr_in sa;
unsigned char buf[BUFSIZ];

main( int argc, char *argv[] )
{
	int i, j;


	if (argc > 2 && !strcmp(argv[1], "-p")) {
		port = atoi(argv[2]);
		argv += 2;
		argc -= 2;
	}

	if (argc != 1) {
		fprintf(stderr, "usage: %s [-p port]\n", argv[0]);
		exit(1);
	}

#ifdef USE_WINSOCK
	{
		WORD wVersionRequested;
		WSADATA wsaData;

		wVersionRequested = MAKEWORD(2, 0);
		i = WSAStartup(wVersionRequested, &wsaData);
		if (i != 0) {
			fprintf(stderr,"WSAStartup failed %d\n", i);
			exit(1);
		}
	}
#endif
	if ((udp = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
		sock_err("udp socket");
		exit(1);
	}

	sa.sin_family = AF_INET;
	sa.sin_port = htons(port);
	sa.sin_addr.s_addr = INADDR_ANY;

	if (bind(udp, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
		sock_err("udp bind");
		exit(1);
	}

	for(;;) {
		unsigned char *ptr, *ptr2, *end;
		int len = sizeof(buf);

		i = recv(udp, buf, len, 0);
		if (i<0) {
			sock_err("recv data");
			exit(1);
		}
		buf[i] = '\0';
		end = buf+i;

		switch(buf[0]) {
		case 0x04:	/* SDMF */
			buf[buf[1]+2] = '\0';	/* wipe out checksum byte */
			ptr = buf;
			printf("%.2s/%.2s %.2s:%.2s %s",
				ptr+2, ptr+4, ptr+6, ptr+8, ptr+10);
			break;
		case 0x80:	/* MDMF */
			buf[buf[1]+2] = '\0';	/* wipe out checksum byte */
			ptr = buf+2;
			while(*ptr) {
			len = ptr[1];
			if (ptr+len+1 > end)	/* sanity check */
				len = end - ptr - 1;
			switch(*ptr) {
			case 0x01:	/* Date & Time */
				printf("%.2s/%.2s %.2s:%.2s",
				ptr+2, ptr+4, ptr+6, ptr+8);
				break;
			case 0x02:	/* Number */
				if (ptr[2] == 'O')
					printf(" UNAVAIL");
				else if (ptr[2] == 'P')
					printf(" PRIVATE");
				else
					printf(" %.*s", len, ptr+2);
				break;
			case 0x04:	/* Number not present */
				break;
			case 0x07:	/* Name */
				printf(" (%.*s)", len, ptr+2);
				break;
			case 0x08:	/* Name not present */
				break;
			default:
				printf(" Unknown data type %2x, %.*s", *ptr, len, ptr+2);
				break;
			}
			ptr += len + 2;
			}
			break;
		}
		putchar('\n');
	}
}
