/*	
 *	clipslip.c
 *
 *	SLIP(Serial Line Internet Protocol)サーバ
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2005 Naoyuki Sawa
 *
 *	* Sun Jul 24 11:35:00 JST 2005 Naoyuki Sawa
 *	- 作成開始。
 */
#include "clip.h"

/****************************************************************************
 *	
 ****************************************************************************/

typedef struct _SLIP {
	int mtu;				/* 最大転送単位 (既定値=1006バイト) */
	//
	unsigned char* rxbuf/*[mtu]*/;		/* 受信バッファ */
	int rxphase;				/* 受信フェーズ */
	int rxlen;				/* 受信バイト数 */
	int rxlog;				/* 総受信パケット数 (エラーパケットも含みます) */
	void (*rxdone)();			/* 受信完了コールバック (NULL=無し) */
	//
	unsigned char* txbuf/*[mtu]*/;		/* 送信バッファ */
	int txphase;				/* 送信フェーズ */
	int txlen;				/* 送信バイト数 */
	int txlog;				/* 総送信パケット数 */
	void (*txdone)();			/* 送信完了コールバック (NULL=無し) */
	//
	char client[6];				/* "CLIENT"検出用バッファ */
	int cslog;				/* "CLIENT"→"CLIENTSERVER"ハンドシェイク回数 */
	//					/* (受信パケット中に偶然含まれた"CLIENT"に対する、無用な応答回数も含みます) */
} SLIP;
static SLIP slip;

/* SLIP.phase */
#define SLIP_IDLE		0x00		/* 送信停止中、または、受信停止中 */
//
#define SLIP_RX_NORMAL		0x10		/* 通常の受信状態 */
#define SLIP_RX_ESCAPE		0x11		/* ESC($DB)の受信直後 */
#define SLIP_RX_DONE		0x12		/* 受信パケット完成、読み出し待ち */
//
#define SLIP_TX_CLIENTSERVER	0x20		/* "CLIENTSERVER"送出中 */
#define SLIP_TX_START		0x21		/* 先頭のEND($C0)送出待ち */
#define SLIP_TX_NORMAL		0x22		/* 通常の送信状態 */
#define SLIP_TX_ESCAPE		0x23		/* ESC($DB)の送信直後 */
#define SLIP_TX_DONE		0x24		/* パケット送出完了、USB I/Oの完了通知待ち */

/****************************************************************************
 *	★割り込み禁止状態★で使用する、内部関数
 ****************************************************************************/

static void rx_isr();
static void tx_isr();
static void rx_sub();
static void tx_sub();

static void
rx_isr()
{
	for(;;) {
		/* 受信パケットを組み立てます。 */
		rx_sub();

		/* 受信パケットが未完成ならば、抜けます。 */
		if(slip.rxphase != SLIP_RX_DONE) {
			break;
		}

		/* 受信完了コールバックへ通知します。 */
		if(slip.rxdone) {
			slip.rxdone();
		}

		/* 受信パケットが読み出されずに残っていたら、抜けます。 */
		if(slip.rxphase == SLIP_RX_DONE) {
			break;
		}
	}
}

static void
tx_isr()
{
	/* USB I/Oは、送出データが無い間も常に、0バイトのUSBパケットを送出し続けています。
	 * USB I/Oの送信完了コールバックは、0バイトのUSBパケット送信完了後にも発生します。
	 * SLIPは、0バイト送出後のコールバックに関心がないので、もしそうならば無視します。
	 */
	if(slip.txphase == SLIP_IDLE) {
		return;
	}

	for(;;) {
		/* 送信パケットを送出します。 */
		tx_sub();

		/* 送信パケットが残っていたら、抜けます。 */
		if(slip.txphase != SLIP_TX_DONE) {
			break;
		}

		/* 送信完了コールバックへ通知します。 */
		if(slip.txdone) {
			slip.txdone();
		}

		/* 次の送信パケットが設定されなかったら、アイドル状態へ移行して抜けます。
		 * (次回、送信パケットが設定されるまで、送信完了コールバックは不要です。)
		 */
		if(slip.txphase == SLIP_TX_DONE) {
			slip.txphase = SLIP_IDLE;
			break;
		}
	}
}

static void
rx_sub()
{
	int retval;
	unsigned char v;

	/* 受信パケットが完成するか、または、明示的にループを抜けるまで... */
	while(slip.rxphase != SLIP_RX_DONE) {

		/* USB I/Oから1バイト受信します。 */
		retval = uio_recv(&v, 1);

		/* UIO I/Oの受信バッファにデータが無くなったら、抜けます。 */
		if(!retval) {
			break;
		}

		/* USB I/Oの受信オーバーランならば、リセットします。 */
		if(retval < 0) {
			uio_reset();
			break;
		}

		/* "CLIENT"検出〜応答待ち以外ならば、"CLIENT"検出用バッファへシフトインします。 */
		if(memcmp(slip.client, "CLIENT", 6) != 0) {
			memmove(&slip.client[0], &slip.client[1], 6 - 1);
			slip.client[5] = v;

			/* "CLIENT"を検出したら、"CLIENTSERVER"の送出開始を試みます。
			 * ここで送出開始できなければ、次回以降のtx_isr()で送出開始します。
			 * * 受信パケット中に偶然含まれた"CLIENT"に対しても、"CLIENTSERVER"を応答してしまいますが、問題ありません。
			 *   前回の送信フレームの終端END($C0)と、次回の送信フレームの先頭END($C0)に挟まれたフレームとして解釈され、
			 *   受信側にてSLIPフレーム→IPパケット展開後、不正なIPヘッダを持つエラーパケットとして破棄されるはずです。
			 */
			if(memcmp(slip.client, "CLIENT", 6) == 0) {
				tx_sub();
				/* 送出開始できた場合には、tx_sub()内にて"CLIENT"検出がクリアされます。 */
			}
		}

		switch(slip.rxphase) {
		case SLIP_IDLE:
			/* 通常状態へ移行します。 */
			slip.rxphase = SLIP_RX_NORMAL;
			slip.rxlen = 0; /* 必要 */
			/* FALLTHRU */
		case SLIP_RX_NORMAL:
			switch(v) {
			case 0xc0:
				/* END($C0)ならば、IPデータグラムの区切りです。 */
				if(slip.rxlen) {
					/* 受信完了状態へ移行します。 */
					slip.rxphase = SLIP_RX_DONE;
					/* 総受信パケット数をカウントアップします。 */
					slip.rxlog++;
				} else {
					/* 空パケットならば、何もせずにアイドル状態へ戻ります。 */
					slip.rxphase = SLIP_IDLE;
				}
				break;
			case 0xdb:
				/* ESC($DB)ならば、エスケープ状態へ移行します。 */
				slip.rxphase = SLIP_RX_ESCAPE;
				break;
			default:
				/* 受信バッファへ格納します。 */
				if(slip.rxlen < slip.mtu) {
					slip.rxbuf[slip.rxlen++] = v;
				}
				break;
			}
			break;
		case SLIP_RX_ESCAPE:
			switch(v) {
			case 0xdc:
				/* $DB,$DC -> $C0 */
				if(slip.rxlen < slip.mtu) {
					slip.rxbuf[slip.rxlen++] = 0xc0;
				}
				break;
			case 0xdd:
				/* $DB,$DD -> $DB */
				if(slip.rxlen < slip.mtu) {
					slip.rxbuf[slip.rxlen++] = 0xdb;
				}
				break;
			default:
				/* 有り得ないバイト並びです。ここでは単純に無視します。
				 * 上位レイヤにて、エラーパケットと判断されるはずです。
				 */
				break;
			}
			/* 通常状態へ戻ります。 */
			slip.rxphase = SLIP_RX_NORMAL;
			break;
		}
	}
}

static void
tx_sub()
{
	int retval;
	unsigned char v;

L_REDO:

	/* 送信パケットが無くなるか、または、明示的にループを抜けるまで... */
	while((slip.txphase != SLIP_IDLE) && (slip.txphase != SLIP_TX_DONE)) {

		switch(slip.txphase) {
		case SLIP_TX_CLIENTSERVER:
			/* "CLIENTSERVER"を送出します。 */
			v = "CLIENTSERVER"[slip.txlen];
			retval = uio_send(&v, 1);
			/* USB I/Oの送信バッファに空きが無ければ、抜けます。 */
			if(!retval) {
				goto L_EXIT;
			}
			/* 次の送出位置を進めます。 */
			slip.txlen++;
			/* "CLIENTSERVER"を送出完了したら、送信完了状態へ移行します。 */
			if(slip.txlen == 12) {
				slip.txphase = SLIP_TX_DONE;
				/* "CLIENT"→"CLIENTSERVER"ハンドシェイク回数をカウントアップします。 */
				slip.cslog++;
			}
			break;

		case SLIP_TX_START:
			/* 先頭のEND($C0)を送出します。 */
			v = 0xc0;
			retval = uio_send(&v, 1);
			/* USB I/Oの送信バッファに空きが無ければ、抜けます。 */
			if(!retval) {
				goto L_EXIT;
			}
			/* 通常状態へ移行します。 */
			slip.txphase = SLIP_TX_NORMAL;
			break;

		case SLIP_TX_NORMAL:
			if(slip.txlen < slip.mtu) {
				/* 次の送出キャラクタを取得します。 */
				v = slip.txbuf[slip.txlen];
				switch(v) {
				case 0xc0:
				case 0xdb:
					/* ESC($DB)を送出します。 */
					v = 0xdb;
					retval = uio_send(&v, 1);
					/* USB I/Oの送信バッファに空きが無ければ、抜けます。 */
					if(!retval) {
						goto L_EXIT;
					}
					/* エスケープ状態へ移行します。(送出位置はそのまま) */
					slip.txphase = SLIP_TX_ESCAPE;
					break;
				default:
					/* USB I/Oへ送出します。 */
					retval = uio_send(&v, 1);
					/* USB I/Oの送信バッファに空きが無ければ、抜けます。 */
					if(!retval) {
						goto L_EXIT;
					}
					/* 次の送出位置を進めます。 */
					slip.txlen++;
					break;
				}
			} else {
				/* 終端のEND($C0)を送出します。 */
				v = 0xc0;
				retval = uio_send(&v, 1);
				/* USB I/Oの送信バッファに空きが無ければ、抜けます。 */
				if(!retval) {
					goto L_EXIT;
				}
				/* 送信完了状態へ移行します。 */
				slip.txphase = SLIP_TX_DONE;
				/* 総送信パケット数カウントアップします。 */
				slip.txlog++;
			}
			break;

		case SLIP_TX_ESCAPE:
			/* ESC($DB)の後続キャラクタを決定します。 */
			v = slip.txbuf[slip.txlen];
			switch(v) {
			case 0xc0:
				/* $C0 -> $DB,$DC */
				v = 0xdc;
				break;
			case 0xdb:
				/* $DB -> $DB,$DD */
				v = 0xdd;
				break;
			}
			/* USB I/Oへ送出します。 */
			retval = uio_send(&v, 1);
			/* USB I/Oの送信バッファに空きが無ければ、抜けます。 */
			if(!retval) {
				goto L_EXIT;
			}
			/* 通常状態へ戻ります。 */
			slip.txphase = SLIP_TX_NORMAL;
			/* 次の送出位置を進めます。 */
			slip.txlen++;
			break;
		}
	}

L_EXIT:

	/* 送信パケットが無ければ... */
	if((slip.txphase == SLIP_IDLE) || (slip.txphase == SLIP_TX_DONE)) {

		/* "CLIENT"を検出していたら、"CLIENTSERVER"応答状態へ移行します。 */
		if(memcmp(slip.client, "CLIENT", 6) == 0) {
			memset(slip.client, 0, 6); /* "CLIENT"検出をクリア */
			slip.txphase = SLIP_TX_CLIENTSERVER;
			slip.txlen = 0; /* 0〜11文字目まで走査し、12で完了 */
			goto L_REDO;
		}
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
slip_open(int mtu/*=1006*/, void (*rxdone)(), void (*txdone)())
{
	/* まず、確実に切断します。 */
	slip_close();

	/* MTUが省略されていたら、既定値とします。 */
	if(!mtu) {
		mtu = 1006; /* RFC 1055 */
	}

	/* ドライバ構造体を初期化します。 */
	memset(&slip, 0, sizeof slip);
	//
	slip.mtu = mtu;
	slip.rxbuf = malloc(mtu);
	if(!slip.rxbuf) {
		DIE();
	}
	slip.rxdone = rxdone;
	//
	slip.txbuf = malloc(mtu);
	if(!slip.txbuf) {
		DIE();
	}
	slip.txdone = txdone;

	/* USB I/Oを開始します。 */
	uio_open(0/*既定値を使用、調整可*/, 0/*既定値を使用、調整可*/, rx_isr, tx_isr);
}

void
slip_close()
{
	/* USB I/Oを切断します。 */
	uio_close();

	/* 受信バッファを開放します。 */
	free(slip.rxbuf); /* free(NULL)は安全 */

	/* 送信バッファを開放します。 */
	free(slip.txbuf); /* free(NULL)は安全 */

	/* ドライバ構造体をクリアします。 */
	memset(&slip, 0, sizeof slip);
}

int
slip_recv(void* dat/*[len]*/, int len)
{
ENTER_CS;
	/* 受信パケットが完成していなければ... */
	if(slip.rxphase != SLIP_RX_DONE) {
		/* 受信パケットの完成を試みます。 */
		rx_sub();

		/* それでも完成しなければ、何もしません。 */
		if(slip.rxphase != SLIP_RX_DONE) {
			len = 0;
		}
	}
	if(len) {
		/* 受信パケットを読み出します。 */
		if(len > slip.rxlen) {
			len = slip.rxlen;
		}
		memcpy(dat, slip.rxbuf, len);

		/* アイドル状態へ戻ります。 */
		slip.rxphase = SLIP_IDLE;
	}
LEAVE_CS;
	return len;
}

int
slip_send(const void* dat/*[len]*/, int len)
{
ENTER_CS;
	/* 送信パケットが残っていたら、 */
	if((slip.txphase != SLIP_IDLE) && (slip.txphase != SLIP_TX_DONE)) {
		/* 送信パケットの送出を試みます。 */
		tx_sub();

		/* それでも残っていたら、何もしません。 */
		if((slip.txphase != SLIP_IDLE) && (slip.txphase != SLIP_TX_DONE)) {
			len = 0;
		}
	}
	if(len) {
		/* 送信パケットを格納します。 */
		if(len > slip.mtu) {
			len = slip.mtu;
		}
		slip.txlen = slip.mtu - len; /* 後ろ詰め */
		memcpy(&slip.txbuf[slip.txlen], dat, len);

		/* 先頭の$C0送出待ち状態へ移行します。 */
		slip.txphase = SLIP_TX_START;

		/* 送信パケットの送出開始を試みます。
		 * ここで送出開始できなければ、次回以降のtx_isr()で送出開始します。
		 */
		tx_sub();
	}
LEAVE_CS;
	return len;
}

void
slip_stat(SLIPSTAT* stat)
{
ENTER_CS;

	stat->cslog = slip.cslog;
	stat->rxlog = slip.rxlog;
	stat->txlog = slip.txlog;

LEAVE_CS;
}

