/*
 *  tty.c -- RS-232C 入出力関連
 *
 *  revision history:
 *	0.0: May  19, 1996 by Dai ISHIJIMA
 *	0.1: Jul. 27, 1996
 *	0.2: Aug. 16, 1996
 *	0.3: Aug. 24, 1996
 *	0.4: Dec.  2, 1998 (for Paldio Phone Book)
 *	0.5: Jan. 14, 1999 (for Chara-Mail Reader)
 *	0.6: Jun. 29, 2000 (support slower speed, 1200 & 2400 bps)
 *	0.7: Jul.  5, 2000 (keyboard input, default tty)
 */

#include <stdio.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/types.h>
#include <time.h>

#include <stdlib.h>
#include <unistd.h>

#ifndef O_NONBLOCK	/* for Sony NEWS, NEWS-OS 4.x */
#include <stream/xti.h>
#endif

#define FILEMODE 0777
#define TIMEOUT 15

#ifdef _POSIX_VDISABLE
#define CC_UNDEF _POSIX_VDISABLE
#else
#define CC_UNDEF 0
#endif

static struct termios t;
static struct termios new;


/* 読み書き用にオープンする */
int opentty(char *name)
{
    int fd;

    if ((fd = open(name, O_RDWR | O_NONBLOCK, FILEMODE)) <= 0) {
	fprintf(stderr, "can't open %s\n", name);
	exit(1);
    }
    return(fd);
}


/* シリアル速度のチェック */
int speed(int sp)
{
    if (sp == 1200) {
	return(B1200);
    }
    else if (sp == 2400) {
	return(B2400);
    }
    else if (sp == 4800) {
	return(B4800);
    }
    else if (sp == 9600) {
	return(B9600);
    }
    else if (sp == 19200) {
	return(B19200);
    }
    else if (sp == 38400) {
	return(B38400);
    }
#ifdef B57600    
    else if (sp == 57600) {
	return(B57600);
    }
#endif
#ifdef B115200
    else if (sp == 115200) {
	return(B115200);
    }
#endif
    else {
	fprintf(stderr, "%d bps unavailable.  9600 bps selected.\n", sp);
    }
    return(B9600);
}


/* シリアルポートの初期化。速度の設定・RAWモードに */
void ttyinit(int fd, long sp)
{
    int i;

    sp = speed(sp);
    tcgetattr(fd, &t);
    memcpy(&new, &t, sizeof(struct termios));
    new.c_iflag = IGNBRK | IGNPAR;
    new.c_oflag = 0;
    new.c_cflag = CS8 | CREAD | CLOCAL;
    new.c_lflag = 0;
    cfsetispeed(&new, sp);
    cfsetospeed(&new, sp);
    for (i = 0; i < NCCS; i++) {
	new.c_cc[i] = CC_UNDEF;
    }
#ifndef VMIN_NULL
    new.c_cc[VMIN] = t.c_cc[VMIN];
    new.c_cc[VTIME] = t.c_cc[VTIME];
#endif
    tcsetattr(fd, TCSANOW, &new);
}    


/* シリアルポートの設定を元に戻す */
void ttyrestore(int fd)
{
    tcsetattr(fd, TCSANOW, &t);
}


/* シリアルポートを閉じる */
int closetty(int fd)
{
    ttyrestore(fd);
    return(close(fd));
}


/* 文字を送信する */
int ttyputc(int fd, char ch)
{
    int status;
    time_t t;

    t = time(NULL);
    while ((status = write(fd, &ch, 1)) < 1) {
#if 0	
	if (status < 0) {
	    fprintf(stderr, "ttyputc(): write failed\n");
	    return(EOF);
	}
#endif
	if (time(NULL) - t > TIMEOUT) {
	    fprintf(stderr, "ttyputc(): timeout\n");
	    return(EOF);
	}
    }
    return(ch);
}


/* 文字を受信する。受信バッファが空のときは EOF を送る */
int ttygetc(int fd)
{
    unsigned char buf[1];
    
    if (read(fd, &buf[0], 1) != 1) {
	return(EOF);
    }
    return(buf[0]);
}

#ifdef MAIN
/* MAINが定義されているときは、上の関数をチェックするプログラムを生成する */

/*
 *  peeping-com -- Lady Godiva をシリアルポートからのぞき見する :-)
 *
 *  このプログラムは COM1: (/dev/ttyd0) から受信した文字を
 *  COM2: (/dev/ttyd1) に、COM2: からの文字を COM1: に再送
 *  します。RS-232C でやりとりされるデータの解析に最適 (?) です。
 *
 *  コンパイル方法: gcc -DMAIN -o peeping-com tty.c
 *
 *  MAINを定義してコンパイルしたプログラムを使う上での制限事項:
 *     ・「ハッカー (hacker)」を、犯罪者の意味で用いる人が
 *       このプログラムを利用することを禁止します。
 *     ・不可使用悪事 (わるいことにつかうべからず :-)
 *     ・不可使用金儲 (かねもうけにつかうべからず :-)
 */

#include <string.h>
#include <sys/time.h>

#define SPEED 9600		/* シリアルの速度 */
/* #define TTY0 "/dev/ttyd0" */	/* COM1: のデバイスファイル */
/* #define TTY1 "/dev/ttyd1" */	/* COM2: のデバイスファイル */
#define TTY0 "/dev/cuaa0"
#define TTY1 "/dev/cuaa1"
#define ONE2ZERO 1
#define ZERO2ONE 0
#define MAXQ 4096
#define NDUMP 16
#define DUMPTIMEOUT 5
#define EOS '\0'

#define shift --argc; ++argv
char *prog;
static int nq = 0;
static unsigned char queue[MAXQ];


/* 表示可能なASCII文字はそのまま、そうでなければピリオド */
#define dumpchar(ch) (((' ' <= (ch)) && ((ch) <= '~')) ? (ch) : '.')

/* 空白文字のチェック */
#define isblank(ch) (((ch) == ' ') || ((ch) == '\t') || ((ch) == '\n'))

/* 16進数文字 0-9a-zA-Z を 0..15 に変換 */
#define hex(ch) ((('0' <= (ch)) && ((ch) <= '9')) ? (ch) - '0' : \
		 (('a' <= (ch)) && ((ch) <= 'f')) ? (ch) - 'a' + 10 : \
		 (('A' <= (ch)) && ((ch) <= 'F')) ? (ch) - 'A' + 10 : 0)


/* 入力された文字列から、出力先を判断し、\? などの変換を行う */
int convert(char *s, char *t)
{
    int dir;
    static char *convstr = "  s  n\n r\r t\t ";
    char *c;

    /* 先頭の空白文字を飛ばす */
    while ((*t != EOS) && (isblank(*t))) {
	++t;
    }
    if ((*t == '0') || (*t == '1')) {
	/* 先頭に数字があれば、それは出力先を意味する */
	dir = (*t == '1') ? ZERO2ONE : ONE2ZERO;
	++t;
	while (*t != EOS) {
	    if (!isblank(*t)) {
		if (*t == '\\') {
		    ++t;
		    if (*t == 'x') {	/* 16進数2桁で文字コード指定 */
			++t;
			*s = (hex(*t) << 4) | hex(*(t + 1));
			++s;
			++t;
		    }
		    else if ((c = strchr(convstr, *t)) != NULL) {
			*s = *(c + 1);
			++s;
		    }
		    else {
			*s = *t;
			++s;
		    }
		}
		else {
		    *s = *t;
		    ++s;
		}
	    }
	    ++t;
	}
    }
    else {
	dir = -1;
    }
    *s = EOS;
    return(dir);
}    


/* キーボードからの入力があれば、それを返す */
int kbdin(char *s)
{
    char t[BUFSIZ];
    struct timeval timeout;
    int dir;
    fd_set readfds;

    /* 標準入力バッファが空でないかを調べる */
    FD_ZERO(&readfds);
    FD_SET(fileno(stdin), &readfds);
    timeout.tv_sec = timeout.tv_usec = 0;
    select(fileno(stdin) + 1, &readfds, NULL, NULL, &timeout);
    if (FD_ISSET(fileno(stdin), &readfds)) {
	/* バッファに入ってればそれを読む */
	fgets(t, BUFSIZ, stdin);
	dir = convert(s, t);
	return(dir);
    }
    return(-1);
}


/* ダンプ用キューに入ってるデータを出力 */
void flushqueue(int dir)
{
    int i, j;

    for (i = 0; i < nq; i += NDUMP) {
	if (dir == ZERO2ONE) {
	    printf("0->1: ");
	}
	else if (dir == ONE2ZERO) {
	    printf("1->0: ");
	}
	for (j = 0; (j < NDUMP) && (i + j < nq); j++) {
	    printf("%02x ", queue[i + j]);
	    if (j == 7) {
		putchar(' ');
	    }
	}
	for (; j < NDUMP; j++) {
	    printf("   ");
	    if (j == 7) {
		putchar(' ');
	    }
	}
	putchar(' ');
	for (j = 0; (j < NDUMP) && (i + j < nq); j++) {
	    putchar(dumpchar(queue[i + j]));
	}
	putchar('\n');
	fflush(stdout);
    }
    nq = 0;
}


/* 受信or送信する文字をダンプ用キューに溜める */
void enqueue(unsigned char ch, int dir)
{
    if (nq >= MAXQ) {
	flushqueue(dir);
    }
    queue[nq] = ch;
    ++nq;
}


void usage()
{
    fprintf(stderr,
	    "Usage: %s [-speed] [-s speed0] [-t speed1] [tty0 [tty1]]\n",
	    prog);
}


void main(int argc, char *argv[])
{
    int ch;
    int fd0, fd1;
    int dir;
    time_t t;
    char *tty0, *tty1;
    long speed0, speed1;
    int n;
    char s[BUFSIZ];

    prog = *argv;
    shift;
    tty0 = TTY0;
    tty1 = TTY1;
    speed0 = speed1 = SPEED;
    while ((argc > 0) && (argv[0][0] == '-')) {
	if (('0' <= argv[0][1]) && (argv[0][1] <= '9')) {
	    speed0 = speed1 = -atoi(*argv);
	}
	else if (argv[0][1]  == 's') {
	    shift;
	    speed0 = atoi(*argv);
	}
	else if (argv[0][1]  == 't') {
	    shift;
	    speed1 = atoi(*argv);
	}
	else {
	    usage();
	    exit(1);
	}
	shift;
    }
    if (argc > 0) {
	tty0 = *argv;
	shift;
    }
    if (argc > 0) {
	tty1 = *argv;
	shift;
    }
    fd0 = opentty(tty0);
    ttyinit(fd0, speed0);
    fd1 = opentty(tty1);
    ttyinit(fd1, speed1);
    dir = -1;
    t = time(NULL);
    while (1) {
	if ((ch = ttygetc(fd0)) >= 0) {
	    t = time(NULL);
	    ttyputc(fd1, ch);
	    if (dir != ZERO2ONE) {
		flushqueue(dir);
	    }
	    dir = ZERO2ONE;
	    enqueue(ch, dir);
	}
	if ((ch = ttygetc(fd1)) >= 0) {
	    t = time(NULL);
	    ttyputc(fd0, ch);
	    if (dir != ONE2ZERO) {
		flushqueue(dir);
	    }
	    dir = ONE2ZERO;
	    enqueue(ch, dir);
	}
	if (time(NULL) - t > DUMPTIMEOUT) {
	    flushqueue(dir);
	    t = time(NULL);
	    if ((dir = kbdin(s)) >= 0) {
		n = 0;
		while (s[n] != EOS) {
		    ttyputc((dir == ZERO2ONE) ? fd1 : fd0, s[n]);
		    enqueue(s[n], dir);
		    ++n;
		}
		flushqueue(dir);
	    }
	}
    }
    closetty(fd0); closetty(fd1);
}

/* Local Variables: */
/* compile-command:"cc -Wall -DMAIN -o peeping-com tty.c" */
/* End: */
#endif
