/* 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 talks to a modem to receive caller ID info
 * and broadcasts it to the local network. The modem spits
 * out the data in hex-dump format; the data is converted
 * back into binary and sent as a single UDP datagram. Aside
 * from the hex-to-binary conversion the format is unchanged
 * since it's already a pretty good transmission format.
 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <termios.h>
#include <string.h>

#define	DEFPORT	10288

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

#define	STRLENOF(string)	(sizeof(string)-1)

int xtoi(char *ptr)
{
	int i, j;

	i = ptr[0] & 0x4f;
	if (i > 9)
		i -= 55;
	j = ptr[1] & 0x4f;
	if (j > 9)
		j -= 55;
	return (i << 4) | j;
}

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

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

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

	modem = open(argv[1], O_RDWR);
	if (modem < 0 ) {
		perror("modem open");
		exit(1);
	}
	{
		struct termios ti;

		i = tcgetattr(modem, &ti);
		if (i < 0) {
			perror("modem tcgetattr");
			exit(1);
		}
		/* We want input one line at a time, no echo */
		ti.c_lflag = ICANON;
		cfsetispeed(&ti, B57600);
		cfsetospeed(&ti, B57600);
		i = tcsetattr(modem, TCSANOW, &ti);
		if (i < 0) {
			perror("modem tcsetattr");
			exit(1);
		}
	}

	if ((udp = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
		perror("udp socket");
		exit(1);
	}

	i = 1;
	if (setsockopt(udp, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i)) < 0 ) {
		perror("udp setsockopt BROADCAST");
		exit(1);
	}
	sa.sin_family = AF_INET;
	sa.sin_port = htons(port);
	sa.sin_addr.s_addr = INADDR_BROADCAST;

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

	i = write(modem, "AT\r\n", STRLENOF("AT\r\n"));
	if (i != STRLENOF("AT\r\n")) {
		perror("write AT");
		exit(1);
	}
	for (j=0; j<4; j++) {
		i = read(modem, buf, sizeof(buf));
		if (i<0) {
			perror("read reply1");
			exit(1);
		}
		buf[i] = '\0';
		if (strstr(buf, "OK"))
			break;
	}
	if (j == 4) {
		fprintf(stderr, "read reply1 failed\n");
		exit(1);
	}

	i = write(modem, "ATZ\r\n", STRLENOF("ATZ\r\n"));
	if (i != STRLENOF("ATZ\r\n")) {
		perror("write ATZ");
		exit(1);
	}
	for (j=0; j<4; j++) {
		i = read(modem, buf, sizeof(buf));
		if (i<0) {
			perror("read reply2");
			exit(1);
		}
		buf[i] = '\0';
		if (strstr(buf, "OK"))
			break;
	}
	if (j == 4) {
		fprintf(stderr, "read reply2 failed\n");
		exit(1);
	}

	/* Set modem to raw CID data format */
	i=write(modem, "AT#CID=2\r\n", STRLENOF("AT#CID=2\r\n"));
	if (i != STRLENOF("AT#CID=2\r\n")) {
		perror("write AT#CID=2");
		exit(1);
	}
	for (j=0; j<4; j++) {
		i = read(modem, buf, sizeof(buf));
		if (i<0) {
			perror("read reply3");
			exit(1);
		}
		buf[i] = '\0';
		if (strstr(buf, "OK"))
			break;
	}
	if (j == 4) {
		fprintf(stderr, "read reply3 failed\n");
		exit(1);
	}

	for(;;) {
		char *ptr, *ptr2;

		for (;;) {
			i = read(modem, buf, sizeof(buf));
			if (i<0) {
				perror("read data");
				exit(1);
			}
			if (!strncmp(buf,"04",2) || !strncmp(buf,"80",2))
				break;
		}
		ptr = ptr2 = buf;
		j = xtoi(ptr+2);
		if (j*2 > i)	/* Malformatted data, just drop it */
			continue;
		/* Convert hex back to binary */
		*ptr++ = xtoi(ptr);
		*ptr++ = j;
		ptr2 += 4;	/* Skip type and len, already set */

		for (;j > 0;j--) {
			*ptr++ = xtoi(ptr2);
			ptr2 += 2;
		}
		/* checksum byte */
		*ptr++ = xtoi(ptr2);
		j = ptr - buf;

		i = write(udp, buf, j);
		/* This is a small datagram write, it must occur atomically */
		if (i != j) {
			perror("write data");
			exit(1);
		}
	}
}
