/* bplexec.c
 *	910130 by Phinloda
 *	911209 long_to_str --> ULONG に変更
 *
 * 本プログラムは、Compuserve B Plus Protocol (SM) の一部をサポートする
 *
 * 作者の都合により、B Plus の仕様とは無関係に以下の機能が勝手に追加され
 * ている。不必要なら削ってからインプリメントすること。
 *
 *    Download 時に、既存ファイルへの追加 (download - append mode)
 *    Upload 時に、複数ファイルを一つのファイルとみなして連続転送
 *        (upload - append mode)
 *    Packet 受信時に、最後の 1 バイトを待たずに Check が合致したとみな
 *        して処理を続行するオプション (flying B+)
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "bpl.h"
#include "machine.h"

extern int com_length(void);
extern int com_recv(void);
extern int com_send(int c);
extern int get_file_information(char *filename, long info[]);
extern int kbd_check(void);
extern int second(void);
extern int set_file_time(char *file, long info[]);
extern void com_rts_off(void);
extern void com_rts_on(void);
extern void decout(int i);
extern void hms(UCHAR *s);
extern void ldecout(ULONG l);

#ifdef SUN
extern int fclose(FILE *stream);
extern int fflush(FILE *stream);
extern int fseek(FILE *stream, long int offset, int origin);
extern int printf(char *format, ...);
extern int unlink(char *path);
extern size_t fread(void *ptr, size_t size, size_t n, FILE *stream);
extern size_t fwrite(void *ptr, size_t size, size_t n, FILE *stream);
extern void *malloc(size_t);
extern void *memcpy(void *s1, void *s2, size_t n);
extern void free(void *ptr);
#endif

/* #define BUGMODEM */

#ifdef USE_STANDARD_FUNCTIONS

#define	charout(c) putchar(c)
#define strout(s) fputs((s), stdout)
#define strwarn(s) fputs((s), stderr)

#else

extern void charout(int c); /* putchar(c) */
extern void strout(char *s); /* fputs(s, fd) */
extern void strwarn(char *s); /* fputs((s), stderr) */

#endif

/* STATIC は、いわゆる static であり、このモジュール内で定義され、他のモ
 * ジュールからは参照されないことを示す。
 * LOCAL は、======== に囲まれた範囲内でのみ使われる。
 */
#define	STATIC static
#define	LOCAL static

/*--------------------------------------------------------------------*/
#define	MAX_PACKET_SIZE 1024 /* パケットの body のサイズ */

/* 最初に獲得するパケット保存領域の数。送信時に送信用に 3 つ使われている
 * 状態で、F packet を受信することがあるので注意。<-- really?
 * Warning: 4 で十分か? 3 でもいいか?
 */
#define	PACKET_NUM 4

/* パケットを保存するための型定義。body の内容は、quote を外したものであ
 * る。少なくとも先頭の 1 文字が、パケットの種類を識別する情報 <Type> で、
 * 内容そのものでないことに注意。
 *
 * sequence の内容
 *	'0' .. '9' 使われている
 *  'U' 使われている（アップロードのファイル読み込み中）
 *	0..9 読み込み中の状態。この状態で処理中断の場合、パケットの内
 *    容は無効となる。
 * 上記以外は、使われていない状態をあらわす。ただし、処理としては、使用
 * 中のパケットの判断は、 '0' との比較で行うこと。
 */
typedef struct packet_t {
	int sequence;
	int type;
	int direction;
	int trailer;
	int size;
	UCHAR body[MAX_PACKET_SIZE + 10]; /* 2 ? */
} PACKET;

static PACKET *packets = NULL;

/* 現在処理中、または最後に処理したパケット
 */
static PACKET *current_packet = NULL;

/* 処理中のパケットの <Sequence>。'0'..'9' の値。
 */
static int packet_no = '0';

/* 最後に受け取った ACK、あるいは送った ACK に対する <Sequence>。この
 * <Sequence> に対応するパケットは既に解放されていて、有効なものはこの次
 * の <Sequence> からとなる。
 */
static int packet_ack = '0';

/* T packet 処理時に、どの状態から呼び出されたかを判別するための情報
 */
#define	FROM_TOPLEVEL 0
#define	FROM_DOWNLOAD 1
#define	FROM_UPLOAD 2

#define DIRECTION_IS_UPLOAD 0
#define DIRECTION_IS_DOWNLOAD 1

/* T packet から得たファイル名
 */
static UCHAR *t_filename = NULL;

/* 本当に利用するファイル名。ユーザーの指示があれば、T packet からのファ
 * イル名は無視して、別名を使うことがある。また、障害があった時には、一
 * 時ファイルを利用する場合、ここにその名前を保存する。
 */
static UCHAR *real_filename = NULL;

/* TI packet の情報
 */
typedef struct {
	int valid; /* 0 の場合は、以下のデータは無効 */
	int data_type; /* 'A':ASCII, 'B':Binary */
	int compression; /* 0:no compression */
	int true_name_length; /* <true name> の長さ */
	/* info[0] データ長 (圧縮された値ではない)
	 * info[1] タイムゾーン (分)
	 * info[2] ファイル作成年月日
	 * info[3] ファイル作成時、00:00:00 からの秒数
	 * info[4] ファイル変更年月日
	 * info[5] ファイル変更時
	 */
	long info[6];
	UCHAR *true_name; /* ファイル名 */
} FILE_INFO;

static FILE_INFO ti_information;

#define SEND_F_PACKET 1
#define RECEIVE_F_PACKET 2
static int failure_status = 0;

/* Get_DLE の FSA 状態
 */
#define	S_Get_DLE 1
#define	S_DLE_Seen 2
#define	S_DLE_B_Seen 3
#define	S_Get_Data 4
#define	S_Get_Check 5
#define	S_Get_CRC 6
#define	S_Verify_CRC 7
#define	S_Veify_CKS 8
#define	S_Verify_Packet 9
#define	S_Send_NAK 10
#define	S_Send_ACK 11
#define	S_Send_ENQ 12
#define	S_Resend_Packets 13

static int acknowledge_status = S_Get_DLE;
static int sa_error_count = 0; /* see document */

/* エラーコード。communication port からの信号は 0 〜 255、quote された
 * 文字を区別するために、256 〜 -511 を文字コードとして使うことがある
 * (mask_recv) ため、実際のエラーコードはこの範囲外の値を利用している。
 */
#define	FINISH -258 /* 正常 (no error) */
#define TIMEOUT -257 /* com_recv() 等でタイムアウト発生 */
#define	FAILURE -259 /* Failure Packet */
#define IOERROR -260 /* 回線状態の致命的なエラー */

/* 文字コード
 */
#define	ETX 0x03
#define	ENQ 0x05
#define	DLE 0x10
#define	DC1 0x11
#define	DC3 0x13
#define	NAK	0x15
#define	ESC	0x1A

static UCHAR quotetable[256];

/* Checksum, CRC
 */
#define	CLEAR_CHECK_VALUE 0
#define	ADD_CHECK_VALUE 1
#define	ASK_CHECK_VALUE 2
#define	CM_IS_CHECKSUM 0
#define	CM_IS_CRC 1;

/* + parameters
 */
static int block_size = (512 / 128);
static int check_method = CM_IS_CHECKSUM; /* checksum:0, CRC:1 */
static int estimate_interval = 4;
static int file_information = 1;
static int transport_layer = 0;
static int window_receive = 0;
static int window_send = 0;

#ifdef WITH_RESUME
static int download_resume = 0;
static int upload_resume = 0;
#endif

static UCHAR default_parameters[] = {
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x14, 0x00, 0xd4, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00};

static UCHAR my_defaults[] = {
	0x01, 0x01, 0x08, 0x01, 0x01, 0x00,
	0x14, 0x00, 0xd4, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, /* resume download は option 指定時に有効 */
	0x00, /* resume upload は option 指定時に有効 */
	0x01};

static UCHAR our_defaults[17];

static FNAME_LIST *current_file = NULL;
static UCHAR *dirnames = NULL;
static int append_mode = 0;
static int file_type = 'A'; /* ASCII */
static int force = 0;
static int look_text = 0;
static int set_ftime = 1;
static int stopchar = ' ';
static int verbose = 0;

/* もし、万一、極めてありえないことだが、モデムが最後に受信した文字を、
 * 次の文字が送信されるまで RS232C に送り出さないことがあるような場合が
 * あったとしたら、これは妙案かもしれない。try_to_fly が nz なら、パケッ
 * トの最後の文字（SUM or CRC）を受け取る前に、正常なデータであると判断
 * する。エラーフリーでない場合には、これは危険な策である。
 */
#ifdef BUGMODEM
static int try_to_fly = 0;
static int flying = 0;
#endif

#define	DOWNMODE_NORMAL 0
#define	DOWNMODE_APPEND 1
#define	DOWNMODE_RESUME 2
static int downmode = DOWNMODE_NORMAL;

#define isMSkanji1st(c) (((c) >= 0x81 && (c) <= 0xaf) || \
					   ((c) >= 0xe0 && (c) <= 0xfc)) /* MS-kanji */

#ifdef SUN

static UCHAR pathchar = '/';
#define iskanji1st(c) ((c) & 0x80) /* EUC */

#else

static UCHAR pathchar = '\\';
#define iskanji1st(c) isMSkanji1st(c)

#endif

/* 順序関係の都合で…
 */
STATIC int send_ahead_packet(PACKET *pac);
STATIC int transfer(PACKET *pac, int mode, FILE *fp);

/* to debug
 */
#ifdef DEBUG

#define DMESSAGE printf

#else

#define DMESSAGE

#endif

/*===== Messages =====================================================*/
#ifdef JAPANESE

#ifdef WITH_RESUME
/* want_to_overwrite() */
#define STR_DELETE_AND_CONTINUE "同名のファイルを削除して作業を続けます\n"
#define STR_SAME_FILENAME "同名のファイルがあります。\n"
#define STR_Q_DELETE "削除して作業を続けますか？"
#endif

/* cannot_open_message() */
#define STR_CANT_OPEN_L "ファイル ("
#define STR_CANT_OPEN_R ") はオープンできません\n"

/* decide_to_delete() */
#define STR_DELETE_CURRENT_1 "受信途中のファイル ("
#define STR_DELETE_CURRENT_2 ") を削除しますか？"
#define STR_DELETED "削除しました\n"

/* copy_dirname() */
#define STR_TOO_LONG_PATHNAME "指定のディレクトリ名が長すぎるので無視します\n"

/* open_tmpfile() */
#define STR_CANT_CREATE_L "ファイルが作成できないので、"
#define STR_CANT_CREATE_R " という名前の一時ファイルにダウンロードします\n"

/* ask_filename() */
#define STR_ENTER_FILENAME "ファイル名（リターンのみで処理中止）\n"

/* set_td_filename() */
#define STR_ALREADY_EXIST " は、既に存在します。\n削除して処理を続行しますか"
#define STR_BAD_FILENAME "ファイル名が異常です\n"

/* est_sub() */
#define STR_LEFT_BYTES_PER_SEC " バイト／秒、あと "
#define STR_TO_FINISH " で終了)"

/* mes_filesize() */
#define STR_TOTAL_SIZE "総サイズ: "
#define STR_BYTES " バイト\n"

/* mes_filename() */
#define STR_FILENAME "処理ファイル名: "

/* mes_speed() */
#define STR_SPEED "速度: "
#define STR_BYTES_PER_SEC " バイト／秒\n"

/* s_get_dle() */
#define STR_RETRY "\n再送要求がありました\n"

/* s_verify_crc() */
#define STR_DATA_ERROR "\nデーターエラー、再送中\n"

/* resume_setup() */
#define STR_WAIT_A_WHILE "しばらくお待ちください\n"
#define STR_BAD_FILE "既存ファイルに異常があります\n"
#define STR_EMPTY_FILE "既存ファイル内容は空です\n"

/* td_packet() */
#define STR_INTERRUPT "\n処理を中断します\n"
#define STR_WRITE_FAILURE "書き込み失敗です。処理を中断します\n"
#define STR_NOT_MATCHED "内容が合致しません。新規作成します。\n"
#define STR_GET_FPACKET "中止要求を受け取りました"

/* open_tu_file() */
#define STR_FILENAME_TROUBLE "ファイル名の処理中に異常がありました\n"

/* tu_packet() */
#define STR_READ_TROUBLE "ファイルの読み込み中に異常がありました\n"

/* ti_message() */
#define STR_FILE_INFO "*** ファイル情報 ***\n"
#define STR_DATA_TYPE "・データ形式: "
#define STR_TEXT "テキスト"
#define STR_BINARY "バイナリ"
#define STR_PICTURE "画像"
#define STR_UNKNOWN_TYPE "(不明)"
#define STR_COMPRESSION "・データ圧縮\n"
#define STR_FILESIZE "・ファイルサイズ: "
/* #define STR_BYTES " バイト\n" */
#define STR_TIMEZONE "・タイムゾーン: "
#define STR_CREATE_TIME "・ファイル作成日時: "
#define STR_MODIFY_TIME "・ファイル変更日時: "
#define STR_TI_FILENAME "・ファイル名: "

/* tc_packet() */
#define STR_TRANSFER_COMPLETE "\n−転送終了−\n"
#define STR_CANT_CLOSE "ファイルをクローズできません\n"

/* transfer() */
#define STR_CANT_PROCESS "処理できないデータを受け取りました\n"

/* s_verify_packet() */
#define STR_GET_INTERRUPT ": 中断要求を受け取りました\n"

#else

#ifdef WITH_RESUME
#define STR_DELETE_AND_CONTINUE "Delete old file, and continue\n"
#define STR_SAME_FILENAME "File already exists.\n"
#endif
#define STR_Q_DELETE "Delete it and continue?"
#define STR_CANT_OPEN_L "Can't open: "
#define STR_CANT_OPEN_R "\n"
#define STR_DELETE_CURRENT_1 "Delete current file ("
#define STR_DELETE_CURRENT_2 ") ?"
#define STR_DELETED "Deleted\n"
#define STR_TOO_LONG_PATHNAME "Directory name is too long and ignored.\n"
#define STR_CANT_CREATE_L "Can't create file, make temporary ("
#define STR_CANT_CREATE_R ").\n"
#define STR_ENTER_FILENAME "Filename (return to stop)\n"
#define STR_ALREADY_EXIST " already exists.\nDelete and continue"
#define STR_BAD_FILENAME "Bad filename\n"
#define STR_LEFT_BYTES_PER_SEC " bytes/sec, estimate: "
#define STR_TO_FINISH ")"
#define STR_TOTAL_SIZE "Total: "
#define STR_BYTES " bytes\n"
#define STR_FILENAME "Filename: "
#define STR_SPEED "Speed: "
#define STR_BYTES_PER_SEC " bytes/sec\n"
#define STR_RETRY "\nRetrying\n"
#define STR_DATA_ERROR "\nData error, retrying\n"
#define STR_WAIT_A_WHILE "Wait a while...\n"
#define STR_BAD_FILE "Can't access current file\n"
#define STR_EMPTY_FILE "Current file is empty\n"
#define STR_INTERRUPT "\nStop\n"
#define STR_WRITE_FAILURE "Write failure.  Stop.\n"
#define STR_NOT_MATCHED "Contents mismatched.  Create new one.\n"
#define STR_GET_FPACKET "Received a failure packet\n"
#define STR_FILENAME_TROUBLE "Error in filename operation\n"
#define STR_READ_TROUBLE "File read error\n"
#define STR_FILE_INFO "*** File information ***\n"
#define STR_DATA_TYPE "o Data type: "
#define STR_TEXT "text"
#define STR_BINARY "binary"
#define STR_PICTURE "image"
#define STR_UNKNOWN_TYPE "(unknown)"
#define STR_COMPRESSION "o Data compression\n"
#define STR_FILESIZE "o File size: "
#define STR_BYTES " bytes\n"
#define STR_TIMEZONE "o Time zone: "
#define STR_CREATE_TIME "o File create time: "
#define STR_MODIFY_TIME "o File modify time: "
#define STR_TI_FILENAME "o Filename: "
#define STR_TRANSFER_COMPLETE "\n- Transfer complete -\n"
#define STR_CANT_CLOSE "Can't close\n"
#define STR_CANT_PROCESS "Invalid T packet (not supported?)\n"
#define STR_GET_INTERRUPT ": failure packet\n"

#endif

/*===== Checksum, CRC ================================================*/
LOCAL UWORD crc_table[] = {
	0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
	0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
	0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
	0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
	0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
	0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
	0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
	0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
	0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
	0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
	0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
	0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
	0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
	0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
	0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
	0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
	0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
	0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
	0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
	0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
	0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
	0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
	0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
	0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
	0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
	0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
	0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
	0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
	0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
	0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
	0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
	0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
};

/*--------------------------------------------------------------------*/
/* Checksum または CRC の計算
 * mode == CLEAR_CHECK_VALUE の場合、初期化した上で i の値をカウント
 *      == ADD_CHECK_VALUE の場合、i の値をカウント
 *      == ASK_CHECK_VALUE の場合、現在の値を戻し、i はカウントされない
 * この関数は check_method が 0 で sum、1 で CRC である場合以外を想定し
 * ていない。これ以外の方法をサポートする場合には書き直すこと。
 */
STATIC UWORD check_value(int mode, int i)
{
	static UWORD value;

	switch (mode) {
	case CLEAR_CHECK_VALUE:
		value = check_method ? 0xffff : 0;
		/* fall into next case */

	case ADD_CHECK_VALUE:
		if (check_method) {
			value = crc_table[((value >> 8) ^ i) & 0xff] ^ (value << 8);
		} else {
			value <<= 1;
			if (value > 0xff) {
				value &= 0xff;
				value++;
			}
			value += i;
			if (value > 0xff) {
				value &= 0xff;
				value++;
			}
		}
		break;
	}
	return value;
}

/*===== Quoting ======================================================*/
STATIC void set_our_quotetable(UCHAR *table)
{
	UCHAR bit_pattern;
	int c;
	int i;
	int j;

	for (i = 0; i < 256; i++) {
		quotetable[i] = 0;
	}

	c = 0;
	for (i = 0; i < 8; i++) {
		if (i == 4) { /* Switch to upper control set */
			c = 128;
		}

		bit_pattern = table[i];

		for (j = 0; j < 8; j++) {
			if (bit_pattern & 0x80) {
				quotetable[c] = c + ((c < 0x20) ? 0x40 : -0x20);
			}
			bit_pattern <<= 1;
			c++;
		}
	}
}

/*--------------------------------------------------------------------*/
STATIC void set_quoting_level(int i)
{
	static UCHAR set0[] = {0x94, 0x00, 0xd4, 0x00, 0x00, 0x00, 0x00, 0x00};
	static UCHAR set1[] = {0x14, 0x00, 0xd4, 0x00, 0x00, 0x00, 0x00, 0x00};
	static UCHAR set2[] = {0x14, 0x00, 0xd4, 0x00, 0x00, 0x00, 0x50, 0x00};
	static UCHAR set3[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
	static UCHAR *quoting_set[4] = {set0, set1, set2, set3};

	if (i >= 0 && i < 4) {
		set_our_quotetable(quoting_set[i]);
	}
}

/*===== Communication port ===========================================*/
/*	Wait until any data is received, or timeout.
 *
 *	wait: 1..59
 *
 *	return value:	0..255, valid data
 *					-256..-1, key was hit (code - 256)
 *					-257 timeout
 */
STATIC int com_trecv(int wait)
{
	int i;
	int passed;
	int tstart;

	tstart = -1;

	do {
		if (com_length() > 0) { /* receive */
			return com_recv();
		}
		i = kbd_check();
		if (i == stopchar)
			return i - 256; /* key was hit */
		if (tstart == -1) {	/* only at first */
			tstart = second();
		}
		passed = second() - tstart; /* second() returns 0..59 */
		if (passed < 0)
			passed += 60;
	} while (passed < wait);

	return TIMEOUT;
}

/*--------------------------------------------------------------------*/
#define TIMEOUT_INTERVAL 16

STATIC int bp_com_recv()
{
	int sts;

retry:
	sts = com_trecv(TIMEOUT_INTERVAL);

#ifdef BUGMODEM
	if (flying && (sts >= 0)) {
		flying = 0;
		if (sts == DLE) {
			sts = com_trecv(TIMEOUT_INTERVAL);
		}
		if (sts >= 0) {
			goto retry;
		}
	}
#endif
	return sts;
}

/*--------------------------------------------------------------------*/
/* DLE を読み取った場合には、quote 解除の処理を行った結果を戻す。
 * これ以外の、quote されるべき文字が単独で現れた場合は、区別のために、
 * 本来のコードに 256 を加えた、256 .. 511 の値を戻す。
 * quote 解除は直接演算する。テーブルを参照していないため、予期しない文字が
 * quote された場合の戻り値は保証しない。
 */
STATIC int mask_recv()
{
	int c;

	c = bp_com_recv();
	if (c >= 0) { /* ok */
		if (c == DLE) { /* quote */
			c = bp_com_recv();
			if (c >= 0) { /* ok */
				c += (c < 0x60) ? -0x40 : 0x20;
			}
		} else if (quotetable[c]) {
			/* quote されるべき文字が単独で送られてきた場合 */
			c |= 0x100;
		}
	}
	return c;
}

/*--------------------------------------------------------------------*/
/* string を com_send をつかって送信
 */
STATIC int com_ssend(char *str)
{
	char c;
	int sts;

	while ((c = *str++) != 0) {
		sts = com_send(c);
		if (sts < 0)
			break;
	}
	return sts;
}

/*--------------------------------------------------------------------*/
STATIC int mask_send(int c)
{
	int q;
	int sts;

	c &= 0xff;
	q = quotetable[c];
	if (q) {
		sts = com_send(DLE);
		if (sts < 0)
			return sts;
		c = q;
	}
	return com_send(c);
}

/*===== utilities ====================================================*/
STATIC int next_sequence(int i)
{
	i++;
	if (i > '9')
		i = '0';
	return i;
}

/*--------------------------------------------------------------------*/
STATIC void my_free(void *ptr)
{
	if (ptr != NULL)
		free(ptr);
}

/*--------------------------------------------------------------------*/
/* メッセージ表示。verbose == 0 の場合には、表示を行わない。常に表示する
 * には、strwarn を使う。
 */
STATIC void message(char *s)
{
	if (verbose)
		strout(s);
}

/*--------------------------------------------------------------------*/
STATIC void vmessage(char *s)
{
	if (verbose > 1)
		strout(s);
}

/*--------------------------------------------------------------------*/
#define INTERVAL_LIMIT 7

STATIC int kbd_tcheck(int init)
{
	static int time_start;
	int c;

	if (init) {
		time_start = second();
		return 0;
	}

	while ((c = kbd_check()) == -1) {
		int now;
		int passed;

		/* Y/N の判断を待たせる場合に、DLE + ; を一定時間ごとに送る */
		now = second() - time_start;
		passed = now - time_start;
		if (passed < 0)
			passed += 60;
		if (passed > INTERVAL_LIMIT) {
			com_ssend("\020;");
			time_start = now;
		}
	}
	return c;
}

/*--------------------------------------------------------------------*/
STATIC int my_gets(char *buf, int limit)
{
	char *s;
	int pos = 0;

	s = buf;
	limit--;
	(void) kbd_tcheck(1);
	while (pos < limit) {
		int c;

		c = kbd_tcheck(0);
		if (c == '\010' && pos > 0) {
			pos--;
			s--;
			charout('\010');
		} else if (c == 0x0d) {
			break;
		} else {
			charout(c);
			*s++ = (char) c;
			pos++;
		}
	}
	*s = '\0';
	return 0;
}

/*--------------------------------------------------------------------*/
/* yes or no
 */
STATIC int yn(char *s)
{
	strout(s);
	strout(" (y/n) ");
#ifdef SUN
	fflush(stdout);
#endif
	(void) kbd_tcheck(1); /* start */
	for (;;) {
		switch (kbd_tcheck(0)) {
		case 'y':
		case 'Y':
			strout(" Yes\n");
			return 1;

		case 'n':
		case 'N':
			strout(" No\n");
			return 0;
		}
	}
}

/*--------------------------------------------------------------------*/
STATIC void remove_cr_or_lf(char *s)
{
	while (*s) {
		if (*s == 0x0a || *s == 0x0d) {
			*s = '\0';
			break;
		}
		s++;
	}
}

#ifdef WITH_RESUME
/*--------------------------------------------------------------------*/
STATIC int want_to_overwrite()
{
	if (force) {
		/* force の場合には、verbose == 0 の時に表示しない */
		/* 「同名のファイルを削除して作業を続けます」 */
		message(STR_DELETE_AND_CONTINUE);
		return 1;
	}

	strout(STR_SAME_FILENAME); /* 「同名のファイルがあります。」 */
	return yn(STR_Q_DELETE); /* 「削除して作業を続けますか？」 */
}
#endif

/*--------------------------------------------------------------------*/
STATIC void cannot_open_message()
{
	message(STR_CANT_OPEN_L); /* 「ファイル (」 */
	message((char *) real_filename);
	message(STR_CANT_OPEN_R); /* 「はオープンできません\n」 */
}

/*--------------------------------------------------------------------*/
/* ファイルを削除するかどうかを確認し、削除の要求があれば削除する。
 */
STATIC void decide_to_delete()
{
	if (downmode == DOWNMODE_NORMAL) {
		strout(STR_DELETE_CURRENT_1); /* 「受信途中のファイル (」 */
		strout((char *) real_filename);
		if (yn(STR_DELETE_CURRENT_2)) { /* 「) を削除します か？」 */
			unlink((char *) real_filename);
			strout(STR_DELETED); /* 「削除しました\n」 */
		}
	}
}

/*--------------------------------------------------------------------*/
#define FNAME_WORK_MAX	256 /* v39 では 128 */

LOCAL UCHAR *fname_work;
LOCAL int fname_w_index;
LOCAL int fname_w_error;

/*--------------------------------------------------------------------*/
LOCAL void add_char(int c)
{
	if (fname_w_index < FNAME_WORK_MAX) {
		fname_work[fname_w_index++] = (UCHAR) c;
	} else {
		fname_w_error = 1;
	}
}

/*--------------------------------------------------------------------*/
/* ファイルを読み込む (作成する) ディレクトリが指定されていれば、バッファ
 * 先頭にコピーする。
 */
LOCAL void copy_dirname()
{
	int len;

	fname_work[0] = '\0';
	if (dirnames == NULL)
		return;

	len = strlen(dirnames);
	if (len > FNAME_WORK_MAX - 2) {
		/* 「指定のディレクトリ名が長すぎるので無視します\n"」 */
		message(STR_TOO_LONG_PATHNAME);
	} else {
		UCHAR pathstr[2];
		int c;
		int i;

		strcpy(fname_work, dirnames);
		for (i = 0; i < len; i++) {
			c = fname_work[i];
			if (iskanji1st(c)) { /* 漢字２バイト目の pathchar は処理しない */
				/* 次に pathchar を追加して大丈夫であることを確認 */
				if (i == (len - 1)) {
					fname_work[i] = '\0';
				}
				i++;
			} else {
				if (i == (len - 1) && c == pathchar) {
					fname_work[i] = '\0';
				}
			}
		}
		pathstr[0] = pathchar;
		pathstr[1] = '\0';
		strcat(fname_work, pathstr);
	}
}

/*--------------------------------------------------------------------*/
/* fname_work + fname_w_index 以降にファイル名を追加する。
 * この処理は OS に依存する
 */

#ifdef SUN
/* UNIX version
 * 先頭に pathchar の繰り返しがあれば、pathchar 1 つに置き換える。
 */
LOCAL int copy_body(UCHAR *s)
{
	int pos = 0;

	while (*s) {
		/* 途中で pathchar が現われた場合は、ポジションをクリアする。
		 * pathname/filename のような場合の処理
		 */
		if (*s == pathchar) {
			while (*s == pathchar)
				s++;
			add_char(pathchar);
			pos = 0;
		} else if (*s == '.' && pos == 0) { /* pathchar 直後 */
			if (s[1] == pathchar) {
				s += 2;
				continue;
			}
			if (s[1] != '.' || s[2] != pathchar) { /* .. appeared */
				return -1;
			}
			add_char('.');
			add_char('.');
			add_char(pathchar);
			s += 3;
			while (*s == pathchar)
				s++;
		} else if (*s == ' ') { /* 通常のファイル名 */
			/* ファイル名の間に空白があった場合、それ以後の文字は無視 */
			pos = FNAME_WORK_MAX;
			s++;
		} else {
			if (iskanji1st(s[0])) {
				/* 漢字先頭バイトのみ存在する時は無視 */
				if (s[1] == '\0')
					return -1;
				/* 2 バイト目が漢字であるチェックはせずに、処理している */
				if (pos < FNAME_WORK_MAX - 1) {
					add_char(s[0]);
					add_char(s[1]);
					pos += 2;
				}
				s += 2;
			} else {
				if (pos < FNAME_WORK_MAX) {
					add_char(s[0]);
					pos++;
				}
				s++;
			}
		}
	}

	if (pos == 0)
		return -1;
	add_char('\0');
	return fname_w_error;
}
#else

/* MSDOS 版
 * 先頭に pathchar の繰り返しがあれば、pathchar 1 つに置き換える。
 */
LOCAL int copy_body(UCHAR *s)
{
	int pos = 0;

	while (*s) {
		/* 途中で pathchar が現われた場合は、ポジションをクリアする。
		 * pathname/filename のような場合の処理
		 */
		if (*s == pathchar) {
			while (*s == pathchar)
				s++;
			add_char(pathchar);
			pos = 0;
		} else if (*s == '.') {
			if (pos == 0) { /* pathchar 直後 */
				if (s[1] == pathchar) {
					s += 2;
					continue;
				}
				if (s[1] != '.' || s[2] != pathchar) { /* .. appeared */
					return -1;
				}
				add_char('.');
				add_char('.');
				add_char(pathchar);
				s += 3;
				while (*s == pathchar)
					s++;
			} else if (pos < 9) {
				add_char('.');
				s++;
				pos = 9;
			} else { /* AFTER_BODY */
				return -1;
			}
		} else if (*s == ' ') { /* 通常のファイル名 */
			/* ファイル名の間に空白があった場合、それ以後の文字は無視 */
			if (pos > 0) {
				if (pos < 9) {
					pos = 8;
				} else {
					pos = 12;
				}
			}
			s++;
		} else {
			if (iskanji1st(s[0])) {
				/* 漢字先頭バイトのみ存在する時は無視 */
				if (s[1] == '\0')
					return -1;
				/* 残り文字数が 1 なら無視 */
				/* 2 バイト目が漢字であるチェックはせずに、処理している */
				if (pos == 7 || pos == 8) {
					pos = 8;
				} else if (pos >= 11) {
					pos = 12;
				} else {
					add_char(s[0]);
					add_char(s[1]);
				}
				s += 2;
				pos += 2;
			} else {
				if (pos < 8 || (pos >= 9 && pos <= 11)) {
					add_char(s[0]);
					pos++;
				}
				s++; /* 901105 */
			}
		}
	}

	if (pos == 0)
		return -1;
	add_char('\0');
	return fname_w_error;
}
#endif

/*--------------------------------------------------------------------*/
/* ファイル名作成。正常終了で 0、エラー時は負の値を戻す
 */
STATIC int create_real_filename(UCHAR *s)
{
	int sts;

	/* 作業領域の獲得およびインデックスの初期化 */
	fname_work = (UCHAR *) malloc((size_t) FNAME_WORK_MAX + 1);
	if (fname_work == NULL)
		return -1; /* out of memory */
	fname_w_error = 0;

	/* s が drive name または pathchar で始まっているときは、指定ディレ
	 * クトリは無視する。
	 */
#ifndef SUN
	if (*s != '\0' && s[1] == ':') {
		fname_work[0] = *s;
		fname_work[1] = s[1];
		fname_work[2] = '\0';
		s += 2;
	} else
#endif
	if (*s == pathchar) {
		fname_work[0] = s[0];
		fname_work[1] = '\0';
		s++;
	} else {
		copy_dirname();
		if (s[0] == '.' && s[1] == pathchar)
			s += 2;
	}

	fname_w_index = strlen(fname_work);
	if (copy_body(s)) { /* ファイル名異常 */
		strcpy(fname_work, tmpnam(NULL));
	}

	my_free(real_filename);
	real_filename = (UCHAR *) malloc(strlen(fname_work) + 1);

	if (real_filename != NULL) {
		strcpy(real_filename, fname_work);
		sts = 0;
	} else {
		sts = -1; /* out of memory */
	}
	free(fname_work);
	return sts;
}

/*--------------------------------------------------------------------*/
STATIC FILE *open_tmpfile()
{
	FILE *fp;
	UCHAR *my_tmp;
	char *tmp;

	tmp = tmpnam(NULL);
	my_tmp = (UCHAR *) malloc(strlen(tmp) + 1);
	if (my_tmp == NULL)
		return NULL;
	strcpy(my_tmp, tmp);

	fp = fopen(tmp, "wb");
	if (fp == NULL) {
		free(my_tmp);
	} else {
		/* ディレクトリは current を使う */
		strwarn(STR_CANT_CREATE_L); /* 「ファイルが作成できないので」 */
		strwarn((char *) my_tmp);
		/* 「という名前の一時ファイルにダウンロードします\n」 */
		strwarn(STR_CANT_CREATE_R);
		my_free(real_filename);
		real_filename = my_tmp;
	}
	return fp;
}

/*--------------------------------------------------------------------*/
STATIC FILE *open_file(char *mode)
{
	FILE *fp;

	/* 次の文は、最初 while でループさせるつもりだったので、無理して 1
	 * 行に押し込めたのである。if で書くのなら 2 行に分けた方が見やすい
	 * かもしれないが、将来、ファイル作成失敗の時にインタラクティブにし
	 * たいので、このまま放置する。
	 */
	if ((fp = fopen(real_filename, mode)) == NULL) {
		/* 指定のファイルがオープンできないので、一時ファイルを作成する。
		 * 一時ファイル名を獲得してから、別に malloc した領域にコピーし
		 * たものを使うのは、この後の処理が正常終了した後で、real_filename
		 * (すなわち、my_tmp) を free するため
		 */
		if (*mode == 'r')
			return NULL;
		fp = open_tmpfile();
	}
	return fp;
}

/*--------------------------------------------------------------------*/
STATIC int ask_filename(char *buf)
{
	strout(STR_ENTER_FILENAME); /* 「ファイル名（リターンのみで処理中止）\n」 */
	my_gets(buf, 80);
	remove_cr_or_lf(buf);
	return strlen(buf) ? 0 : -1;
}

/*--------------------------------------------------------------------*/
STATIC int set_td_filename()
{
	char buf[83];
	FILE *fp;
	int sts;

	for (;;) {
		fp = fopen(real_filename, "rb");
		if (fp == NULL) { /* 既存のファイルがなければ、通常の download */
			return DOWNMODE_NORMAL;
		}
		fclose(fp);

		if (append_mode)
			return DOWNMODE_APPEND;
#ifdef WITH_RESUME
		if (download_resume > 0) /* Resume download */
			return DOWNMODE_RESUME;
#endif
		if (force)
			return DOWNMODE_NORMAL;
		if (verbose == 0) {
			/* tempname */
			return -1;
		}
		strout((char *) real_filename);
		/* 「 は、既に存在します。\n削除して処理を続行しますか」 */
		if (yn(STR_ALREADY_EXIST)) {
			return DOWNMODE_NORMAL;
		}
		do {
			sts = 1;
			if (ask_filename(buf)) {
				return -1;
			}
			if (create_real_filename((UCHAR *) buf) < 0) {
				strout(STR_BAD_FILENAME); /* 「ファイル名が異常です\n」 */
			} else {
				sts = 0;
			}
		} while (sts);
	}
}

/*--------------------------------------------------------------------*/
LOCAL ULONG get_current_time()
{
	UCHAR buf[3];
	size_t t;
	static size_t last_time = 0;
	static int day = 0;

	hms(buf); /* get hour, minute, and second */
	t = (size_t) day + buf[0];
	t *= (size_t) 60;
	t += (size_t) buf[1];
	t *= (size_t) 60;
	t += (size_t) buf[2];
	if (t < last_time) {
		t += (size_t) 24 * 60 * 60;
		day += 24;
	}
	return (ULONG) (last_time = t);
}

/*--------------------------------------------------------------------*/
/* 時刻表示のため、1 桁の場合には先頭に '0' を表示する数値表示
 */
LOCAL void decout2(int i)
{
	if (i < 10)
		charout('0');
	decout(i);
}

/*--------------------------------------------------------------------*/
STATIC void showtime(ULONG t)
{
	int i;

	decout2((int) (t / 3600));
	charout(':');
	i = (int) (t % 3600);
	decout2(i / 60);
	charout(':');
	decout2(i % 60);
}

/*--------------------------------------------------------------------*/
STATIC void est_sub(ULONG rest, ULONG rate)
{
	if (rest > 0) {
		strout(" (");
		ldecout(rate);
		strout(STR_LEFT_BYTES_PER_SEC); /* 「 バイト／秒、あと " */
		showtime(rest);
		strout(STR_TO_FINISH); /* 「 で終了)」 */
	}
}

/*--------------------------------------------------------------------*/
/* 処理に必要な残り時間の予想
 * この関数は verbose > 0 の時にだけ呼ぶ。
 * 最初の MES_ESTIMATE_COUNT 回の転送結果は、不安定なため無視する。
 */
#define	MES_ESTIMATE_COUNT 2

LOCAL void mes_estimate(ULONG filesize, ULONG sendsize, int count)
{
	ULONG now;
	static ULONG firstsize;
	static ULONG start;
	static int my_count;

	count -= MES_ESTIMATE_COUNT;
	if (count < 0)
		return;

	now = get_current_time();

	if (count == 0) {
		my_count = 0;
		start = now;
		firstsize = sendsize;
		return;
	}

	my_count++;
	if (my_count >= estimate_interval) {
		ULONG pass_time;
		ULONG rate;
		ULONG rest;
		ULONG send;

		my_count = 0;
		send = sendsize - firstsize;
		pass_time = now - start;
		if (pass_time > 0) {
			rate = send / pass_time;
			if (rate > 0) {
				rest = (filesize - sendsize) / rate;
				est_sub(rest, rate);
			}
		}
	}
}

/*--------------------------------------------------------------------*/
/*	処理データサイズ表示
 */
STATIC void mes_datasize(ULONG filesize, ULONG sendsize, int count)
{
	if (verbose > 0) {
		ldecout(sendsize);
		strout(" bytes");
		if ((filesize > 0) && estimate_interval)
			mes_estimate(filesize, sendsize, count);
		charout('\r');
#ifdef SUN
		fflush(stdout);
#endif
	}
}

/*--------------------------------------------------------------------*/
/*	ファイルサイズ表示
 */
STATIC void mes_filesize(ULONG filesize)
{
	if (verbose > 0) {
		strout(STR_TOTAL_SIZE); /* 「総サイズ: 」 */
		ldecout(filesize);
		strout(STR_BYTES); /* 「バイト\n」 */
	}
}

/*--------------------------------------------------------------------*/
/*	ファイル名表示
 */
STATIC void mes_filename()
{
	message(STR_FILENAME); /* 「処理ファイル名: 」 */
	message((char *) real_filename);
	message("\n");
}

/*--------------------------------------------------------------------*/
/* 転送速度表示
 */
STATIC void mes_speed(ULONG size, ULONG second)
{
	ULONG l;

	if (verbose && second) {
		l = (ULONG) 10 * size / second;
		strout(STR_SPEED); /* 「速度: 」 */
		ldecout(l / 10);
		charout('.');
		ldecout(l % 10);
		strout(STR_BYTES_PER_SEC); /* 「 バイト／秒\n」 */
	}
}

/*--------------------------------------------------------------------*/
LOCAL UCHAR *ltoa_str;

LOCAL void ltoa_sub(ULONG l)
{
	if (l > 9) {
		ltoa_sub(l / 10);
	}
	*ltoa_str++ = (char) (l % 10) + '0';
}

/*--------------------------------------------------------------------*/
STATIC void ulong_to_str(ULONG l, UCHAR *s)
{
	if (s == NULL)
		s = ltoa_str;

	ltoa_str = s;
	ltoa_sub((ULONG) l);
	*ltoa_str = '\0';
}

/*--------------------------------------------------------------------*/
STATIC void add_ulong_to_str(ULONG l, UCHAR *s)
{
	ulong_to_str(l, s);
	*ltoa_str++ = ' ';
	*ltoa_str = '\0';
}

/*===== utilities (packet) ===========================================*/
/* current_packet に、作業用の空パケットを割り当てる
 */
STATIC int reserve_current_packet()
{
	int i;

	for (i = 0; i < PACKET_NUM; i++) {
		if (packets[i].sequence < '0') {
			current_packet = packets + i;
			return 0;
		}
	}
	current_packet = NULL;
	return -1;
}

/*--------------------------------------------------------------------*/
STATIC void init_all_packets()
{
	int i;

	for (i = 0; i < PACKET_NUM; i++) {
		packets[i].sequence = 0;
	}
	current_packet = NULL;
}

/*===== Send a packet ================================================*/
/* 実際にパケット内容を送信する。Send Ahead の確認後に呼び出すこと。
 */
STATIC int send_packet(PACKET *pac)
{
	int i;

	pac->direction = DIRECTION_IS_UPLOAD;
	/* Lead-in */
	com_send(DLE);
	com_send('B');

	/* Sequence */
	if (pac->sequence > '9' || pac->sequence < '0') {
		pac->sequence = next_sequence(packet_no);
	}
	com_send(pac->sequence);

	check_value(CLEAR_CHECK_VALUE, pac->sequence);
	packet_no = pac->sequence;

	/* Type */
	/* Body */
	for (i = 0; i < pac->size; i++) {
		UCHAR data;

		data = pac->body[i];
		mask_send(data);
		check_value(ADD_CHECK_VALUE, data);
	}

	/* Trailer */
	com_send(ETX);
	check_value(ADD_CHECK_VALUE, ETX);
	i = (int) check_value(ASK_CHECK_VALUE, 0);
	/* 次の箇所は、check_method が nz の場合には CRC と断定しているので
	 * 注意
	 */
	if (check_method) {
		mask_send(i >> 8); /* mask_send は、常に下位 8 ビットのみ送る */
	}
	mask_send(i);
	return 0; /* error check は? */
}

/*--------------------------------------------------------------------*/
/* send acknowledge, set pac->sequence to 0
 */
STATIC void send_ack(PACKET *pac)
{
	com_send(DLE);
	packet_ack = pac->sequence;
	com_send(packet_ack);
	pac->sequence = 0; /* 処理済のパケットは解放する */
}

/*===== Receive a packet =============================================*/
LOCAL int after_enq = 0;
LOCAL int data_error = 0;

LOCAL int s_get_dle(int receive)
{
	int c;

	c = bp_com_recv();
	if (c != DLE) {
		after_enq = 0;
	}
	switch (c) {
	case DLE:
		acknowledge_status = S_DLE_Seen;
		break;

	case NAK:
		acknowledge_status = receive ? S_Get_DLE : S_Send_ENQ;
		message(STR_RETRY); /* 「\n再送要求がありました\n」 */
		break;

	case ENQ:
		acknowledge_status = S_Send_ACK;
		break;

	case ETX:
		vmessage("\nget ETX\n");
		acknowledge_status = S_Send_NAK;
		break;

	case TIMEOUT:
		vmessage("\nTimeout\n");
		acknowledge_status = receive ? S_Send_NAK : S_Send_ENQ;
		break;

	default:
		if (c < 0) {
			return c;
		} else {
			acknowledge_status = S_Get_DLE;
		}
	}
	return 0;
}

/*--------------------------------------------------------------------*/
/* sequence で指定された packet、および、それ以前の packet が再送のため
 * に保存されていれば、バッファを未使用の状態にする。
 * sequence = '0'..'9'
 */
STATIC void free_packet(int sequence)
{
	int invalid;

	if (packet_ack < packet_no) {
		invalid = (sequence > packet_no || sequence <= packet_ack);
	} else if (packet_ack > packet_no) {
		invalid = (sequence > packet_no && sequence <= packet_ack);
	} else { /* 送ったパケットはない */
		invalid = 1;
	}

	if (invalid) { /* 送った覚えのないパケットへの ACK */
		return;
	}

	packet_ack--; /* (^_^;) */
	do {
		int i;

		packet_ack = next_sequence(packet_ack);
		for (i = 0; i < PACKET_NUM; i++) {
			if (packets[i].sequence == packet_ack) {
				packets[i].sequence = 0;
				break; /* 同一の sequence のパケットはないはず */
			}
		}
	} while (packet_ack != sequence);
}

/*--------------------------------------------------------------------*/
LOCAL int s_dle_seen(int receive)
{
	int c;

	c = bp_com_recv();
	switch (c) {
	case 'B':
		acknowledge_status = S_DLE_B_Seen;
		break;

	case ';':
		acknowledge_status = S_Get_DLE;
		break;

	case ENQ:
		acknowledge_status = S_Send_ACK;
		break;

	case TIMEOUT:
		vmessage("Timeout\n");
		acknowledge_status = receive ? S_Send_NAK : S_Send_ENQ;
		break;

	default:
		if (c < 0) {
			return c;
		} else if (c >= '0' && c <= '9') { /* Positive Ack */
			if (after_enq) {
				/* ENQ を送った直後には、ACK が 2 個戻ってくるので、これ
				 * が一致した場合には、S_Resend_Packets へ遷移させる。一
				 * 致しなければ、ENQ を再度送信する。
				 */
				static int last_sequence = 0;

				if (after_enq == 2) { /* 1 個目の ACK */
					last_sequence = c;
					acknowledge_status = S_Get_DLE;
				} else { /* 2 個目の ACK */
					acknowledge_status =
						(last_sequence != c) ? S_Send_ENQ : S_Resend_Packets;
				}
				after_enq--;
			} else {
				DMESSAGE("s_dle_seen: ack received %c\n", c);
				free_packet(c);
				if (sa_error_count > 0)
					sa_error_count--;
				return FINISH;
			}
		} else {
			acknowledge_status = S_Get_DLE;
		}
	}
	return 0;
}

/*--------------------------------------------------------------------*/
LOCAL int s_dle_b_seen()
{
	int c;

	c = bp_com_recv();
	switch (c) {
	default:
		if (c < 0) {
			return c;
		} else if (c >= '0' && c <= '9') {
			int sts;

			/* sequence 情報の読み取り… '0'〜'9' でない場合未処理 */
			sts = reserve_current_packet();
			if (sts < 0)
				return sts;
			current_packet->sequence = c - '0';
			current_packet->size = 0;
			check_value(CLEAR_CHECK_VALUE, c);
			acknowledge_status = S_Get_Data;
			data_error = 0;
			break;
		}
		/* fall into next case */

	case TIMEOUT:
		acknowledge_status = S_Send_NAK;
		vmessage("\nTimeout\n");
		break;

	case ENQ:
		acknowledge_status = S_Send_ACK;
		break;
	}
	return 0;
}

/*--------------------------------------------------------------------*/
LOCAL int s_get_data()
{
	int c;

	c = mask_recv();
	switch (c) {
	case ETX | 0x100:
		check_value(ADD_CHECK_VALUE, ETX);
		acknowledge_status = S_Get_Check;
		break;

	case ENQ | 0x100:
		acknowledge_status = S_Send_ACK;
		break;

	case TIMEOUT:
		vmessage("\nTimeout\n");
		acknowledge_status = S_Send_NAK;
		break;

	default:
		if (data_error == 3)
			return 0;
		if (c < 0) {
			/* key が押された場合、残りデータをフラッシュしなくてよいのか ? */
			if (c >= -256) {
				data_error = 3;
				return 0;
			}
			else
				return c;
		} else if (c < 256) {
			check_value(ADD_CHECK_VALUE, c);
			if (current_packet->size < MAX_PACKET_SIZE + 2) { /* 901127 */
				current_packet->body[current_packet->size++] = (UCHAR) c;
			} else {
				data_error = 1;
			}
			/* acknowledge_status は変化しない */
		} else {
			data_error = 2;
		}
	}
	return data_error == 3 ? FAILURE : 0;
}

/*--------------------------------------------------------------------*/
LOCAL int s_get_check(UWORD *checksum)
{
	int c;

#ifdef BUGMODEM
	if (check_method == 0 && try_to_fly) {
		flying = 1;
		acknowledge_status = S_Veify_CKS;
		return 0;
	}
#endif

	c = mask_recv(); /* checksum は quote されることがある */
	if (c == TIMEOUT || c > 255) {
		vmessage("\nTimeout\n");
		acknowledge_status = S_Send_NAK;
	} else if (c < 0) {
		return c; /* error or interrupt */
	} else {
		acknowledge_status = check_method ? S_Get_CRC : S_Veify_CKS;
		*checksum = (UWORD) c & 0xff;
	}
	return 0;
}

/*--------------------------------------------------------------------*/
LOCAL int s_get_crc(UWORD *crc)
{
	int c;

#ifdef BUGMODEM
	if (try_to_fly) {
		flying = 1;
		acknowledge_status = S_Verify_CRC;
		return 0;
	}
#endif
	c = mask_recv(); /* CRC は quote されることがある */
	if (c == TIMEOUT || c > 255) {
		vmessage("\nTimeout\n");
		acknowledge_status = S_Send_NAK;
	} else if (c < 0) {
		return c; /* error or interrupt */
	} else {
		acknowledge_status = S_Verify_CRC;
		*crc = (*crc << 8) + c;
	}
	return 0;
}

/*--------------------------------------------------------------------*/
LOCAL void s_verify_crc(UWORD crc)
{
	UWORD val;

#ifdef BUGMODEM
	if (flying) {
		acknowledge_status = S_Verify_Packet;
		return;
	}
#endif

	val = check_value(ASK_CHECK_VALUE, 0);

	if (val == crc && data_error == 0) {
		acknowledge_status = S_Verify_Packet;
	} else {
		message(STR_DATA_ERROR); /* 「\nデーターエラー、再送中\n」 */
		acknowledge_status = S_Send_NAK;
	}
}

#define s_verify_cks s_verify_crc

/*--------------------------------------------------------------------*/
/* sequence を確認する。
 *	既存パケットの次の番号であれば、終了。
 *	F packet なら、無条件に終了。
 *	既存パケットの最後の番号であれば、ACK を戻す。
 *	それ以外の場合には NAK を戻す。
 */
#define	ERROR_MAX_COUNT 16
#define ERROR_CLEAR_COUNT 2
LOCAL int nac_count = 0;
LOCAL int ok_count = 0;

LOCAL int s_verify_packet()
{
	int sequence;
	int next_seq;

	current_packet->sequence += '0';
	if (current_packet->body[0] == 'F') {
		current_packet->body[current_packet->size] = '\0';
		message((char *) current_packet->body + 2);
		message(STR_GET_INTERRUPT);
		failure_status = RECEIVE_F_PACKET;
		send_ack(current_packet); /* これでよいか ? */
		return FAILURE;
	}

	if (nac_count > 0) {
		if (++ok_count >= ERROR_CLEAR_COUNT) {
			nac_count--;
			ok_count = 0;
		}
	}

	next_seq = next_sequence(packet_no);
	sequence = current_packet->sequence;
	if (sequence == next_seq) {
		packet_no = next_seq;
		return FINISH;
	}

	current_packet->sequence = 0; /* free this packet */
	acknowledge_status = sequence == packet_no ? S_Send_ACK : S_Send_NAK;
	return 0;
}

/*--------------------------------------------------------------------*/
/* エラー発生時のカウントは、次の通り
 * nak を送信するごとに、
 *		nac_count に 1 を加える
 *		ok_count を 0 とする
 *		nac_count が ERROR_MAX_COUNT を超えたら、異常終了
 * nac_count > 0 なら、ack を送信するごとに、
 *		ok_count に 1 を加える
 *		ok_count が ERROR_CLEAR_COUNT 以上なら、
 *			nac_count から 1 を引く
 *			ok_count を 0 とする
 */
LOCAL int s_send_nak()
{
	acknowledge_status = S_Get_DLE;
	nac_count++;
	ok_count = 0;
	if (nac_count > ERROR_MAX_COUNT) {
		return IOERROR;
	}

	if (failure_status)
		return 0;

	return com_send(NAK);
}

/*--------------------------------------------------------------------*/
LOCAL void s_send_ack()
{
	acknowledge_status = S_Get_DLE;
	com_send(DLE);
	com_send(packet_no); /* 最後に正常に処理したパケット */
}

/*--------------------------------------------------------------------*/
LOCAL void s_send_enq()
{
	acknowledge_status = S_Get_DLE;
	com_send(ENQ);
	com_send(ENQ);
	after_enq = 2;
}

/*--------------------------------------------------------------------*/
LOCAL int s_resend_packets()
{
	int last_packet;
	int sts;

	sa_error_count += 3;
	if (sa_error_count >= 12) {
		window_receive = 0;
	}

	last_packet = packet_no;
	packet_no = packet_ack;

	do {
		PACKET *pac;
		int i;
		int next_packet_no;

		next_packet_no = next_sequence(packet_no);
		pac = NULL;
		for (i = 0; i < PACKET_NUM; i++) {
			if (packets[i].sequence == next_packet_no) {
				pac = packets + i;
				break;
			}
		}

		if (pac != NULL) {
			pac->sequence = 'U';
			sts = send_ahead_packet(pac); /* packet_no added */
			if (sts < 0)
				break;
		} else {
			/* 送ったはずのパケットがない ? */
			packet_no = next_packet_no;
			DMESSAGE("s_resend_packet: lost packet asked %c\n", packet_no);
			sts = 0;
			break;
		}
	} while (last_packet != packet_no);

	/* Send all un-ACKed packets */
	acknowledge_status = S_Get_DLE;
	return sts;
}

/*--------------------------------------------------------------------*/
/* ACK 待ち状態の FSA
 * ack が得られたら、0 を戻す。
 * packet を受け取った場合は、1 を戻す。内容は current_packet。
 * error の場合は < 0
 */
STATIC int wait_for_acknowledge(int receive)
{
	UWORD checksum;
	int sts = 0;

	after_enq = 0;
	while (sts >= 0) {
		switch (acknowledge_status) {
		case S_Get_DLE:
			sts = s_get_dle(receive);
			break;

		case S_DLE_Seen:
			sts = s_dle_seen(receive);
			break;

		case S_DLE_B_Seen:
			sts = s_dle_b_seen();
			break;

		case S_Get_Data:
			sts = s_get_data();
			break;

		case S_Get_Check:
			sts = s_get_check(&checksum);
			break;

		case S_Get_CRC:
			sts = s_get_crc(&checksum);
			break;

		case S_Verify_CRC:
			s_verify_crc(checksum);
			sts = 0;
			break;

		case S_Veify_CKS:
			s_verify_cks(checksum);
			sts = 0;
			break;

		case S_Verify_Packet:
			sts = s_verify_packet();
			break;

		case S_Send_NAK:
			sts = s_send_nak();
			break;

		case S_Send_ACK:
			s_send_ack();
			sts = 0;
			break;

		case S_Send_ENQ:
			s_send_enq();
			sts = 0;
			break;

		case S_Resend_Packets:
			sts = s_resend_packets();
			break;
		}
	}

	if (sts == FINISH) {
		sts = acknowledge_status == S_Verify_Packet ? 1: 0;
	}
	return sts;
}

/*===== Read a packet ================================================*/
/* これを呼び出して、NULL を戻した場合には、既に Failure Packet が送信さ
 * れているので、terminal mode まで復帰すること。
 */
STATIC PACKET *read_packet(int initial_status)
{
	int i;
	int next;

	acknowledge_status = initial_status;
	while (packet_ack == packet_no) {
		if (wait_for_acknowledge(1) < 0) {
			return NULL;
		}
	}

	next = next_sequence(packet_ack);
	for (i = 0; i < PACKET_NUM; i++) {
		if (packets[i].sequence == next)
			return packets + i;
	}
	return NULL; /* lost! */
}

/*===== Send ahead a packet ==========================================*/
/* 指定された数までの先送りができるという前提で、ACK を待つ
 * 従って、0 で呼び出せば、送信したパケットに対する ACK を全て回収する。
 * この関数が負の値を戻した場合は、Failure Packet が既に送信されている。
 *
 * mode == 0 の場合は、ACK があるまで待つ。
 * mode == 1 の場合は、パケットを受信した場合には 1 を戻す。
 * いずれの場合も、failure packet を受信した場合は -1 を戻す。
 */
STATIC int wait_ack(int ahead_allowed, int mode)
{
	int sts = 0;
	int send_ahead;

	DMESSAGE("wait_ack(%d, %d): pacno=%c, ack=%c\n",
		ahead_allowed, mode, packet_no, packet_ack);
	do {
		send_ahead = packet_no - packet_ack;
		if (send_ahead < 0)
			send_ahead += 10;
		if (send_ahead <= ahead_allowed)
			break;
		acknowledge_status = S_Get_DLE;
		sts = wait_for_acknowledge(0);
	} while (mode ? sts == 0 : sts >= 0);

	return sts;
}

/*--------------------------------------------------------------------*/
STATIC int send_ahead_packet(PACKET *pac)
{
	int sts;

	sts = wait_ack(window_receive, 0);
	if (sts >= 0) {
		/* 実際に送信。pac->sequence が 'U' なら、自動的に最新の sequence
		 * が割り当てられ、カウントアップする。
		 */
		sts = send_packet(pac);
	}
	return sts;
}

/*--------------------------------------------------------------------*/
STATIC int send_current_packet(int size)
{
	current_packet->sequence = 'U';
	current_packet->size = size;
	return send_ahead_packet(current_packet);
}

/*--------------------------------------------------------------------*/
STATIC int send_tx_packet(char *s)
{
	int sts;

	sts = reserve_current_packet();
	if (sts == 0) {
		strcpy(current_packet->body, s);
		if (s[1] == 'U' || s[1] == 'D') {
			strcat(current_packet->body, real_filename);
		}
		sts = send_current_packet(strlen(current_packet->body));
	}
	return sts;
}

/*===== Failure packet ==============================================*/
/* Failure packet 送信。バッファ上の全パケットは無効になる。
 * current_packet は一旦クリアされる。
 */
STATIC void send_failure_packet(char *s)
{
	if (failure_status == 0) {
		init_all_packets();
		DMESSAGE("send_FP (%s)\n", s);
		(void) reserve_current_packet(); /* 必ず成功する */
		failure_status = SEND_F_PACKET; /* F packet 送信 */
		current_packet->body[0] = 'F';
		strcpy(current_packet->body + 1, s);

		/* このパケットを送信し、ACK を待つ。手順に問題あり？ */
		(void) send_current_packet((int) strlen(current_packet->body));
		/* (void) wait_ack(0, 0); */
	}
}

/*===== TI packet ====================================================*/
/* TI < 1 の場合には、呼び出されない
 */
STATIC int send_ti_packet()
{
	UCHAR *s;
	int i;

	if (reserve_current_packet())
		return -1;
	s = current_packet->body;
	*s++ = 'T';
	*s++ = 'I';
	*s++ = (char) ti_information.data_type;
	*s++ = (char) ti_information.compression;
	for (i = 0; i < 6; i++) {
		add_ulong_to_str(ti_information.info[i], i ? NULL : s);
	}
	strcat((char *) s, ti_information.true_name);
	return send_current_packet((int) (strlen((char *) s) + 4));
}

/*===== File transfer ================================================*/
/* これらの変数は、以下の関数でのみ使われている。
 *	scan_byte()
 *	scan_number()
 *	ti_packet()
 */
static UCHAR *scan_ptr;
static UCHAR *scan_end;
static int scan_error;

/*--------------------------------------------------------------------*/
STATIC void scan_init(PACKET *pac, int skip)
{
	/* pac->body の大きさにくらべて、pac->size は十分小さいことを想定し
	 * ている。scan_end は、必ず pac->body の中に含まれる前提。
	 */
	scan_ptr = pac->body + skip;
	scan_end = pac->body + pac->size;
	scan_error = 0;
}

/*--------------------------------------------------------------------*/
/* 1 byte *scan_ptr からスキャンし、ポインタを移動する。
 * オーバーランの場合には scan_error を 1 とし、関数は 0 を戻す。
 */
static int scan_byte()
{
	if (scan_ptr >= scan_end) {
		scan_error = 1;
		return 0;
	}
	return (int) *scan_ptr++;
}

#if 1
/*--------------------------------------------------------------------*/
static long scan_number()
{
	int i;
	int minus = 0;
	int scan = 0;
	long l = (long) 0;

	while (scan_ptr < scan_end) {
		i = (int) *scan_ptr++;
		if (i >= '0' && i <= '9') {
			scan = 2;
			l *= (long) 10;
			l += (long) (i - '0');
		} else if (i == '-') {
			if (scan == 0) {
				minus = 1;
				scan = 1;
			} else {
				scan_error = 1;
				break;
			}
		} else if (i == ' ') {
			if (scan == 2 || scan == 0) { /* 空白を読み飛ばす */
				while ((scan_ptr < scan_end) && (*scan_ptr == ' '))
					scan_ptr++;
				if (scan)
					break;
			} else {
				scan_error = 1;
				break;
			}
		} else {
			scan_error = 1;
			break;
		}
	}

	return minus ? -l : l;
}

#else

/*--------------------------------------------------------------------*/
LOCAL void skip_space()
{
	while (*scan_ptr == ' ')
		scan_ptr++;
}

LOCAL long scan_long()
{
	long l = (long) 0;
	int i;

	for (;;) {
		i = (int) *scan_ptr;
		if (i >= '0' && i <= '9') {
			l *= (long) 10;
			l += (long) (i - '0');
		} else {
			if (i == 0)
				scan_error = 1;
			break;
		}
		scan_ptr++;
	}
	return l;
}

static long scan_number()
{
	long l;
	int minus;

	skip_space();
	if (*scan_ptr == '-') {
		scan_ptr++;
		minus = 1;
	} else {
		minus = 0;
	}
	l = scan_long();
	return minus ? -l : l;
}

#endif

#ifdef WITH_RESUME
/*--------------------------------------------------------------------*/
LOCAL void str_check_value(UCHAR *buf, int n)
{
	while (n-- > 0) {
		check_value(ADD_CHECK_VALUE, (int) *buf++);
	}
}

/*--------------------------------------------------------------------*/
/* resume 開始のために、ファイルから指定した長さを読み込み、CRC を計算す
 * る。length に -1 を指定した場合には、ファイルをすべて読み込む。
 * [戻り値]
 *		正常終了	読み込んだ長さ
 *		異常終了	-1
 */
STATIC long resume_setup(long length)
{
	FILE *fp;
	UCHAR *buf; /* 作業用領域は空きパケットを利用 */
	ULONG now;
	ULONG start_time;
	int sts;
	long file_size;
	long size;
	size_t n;

	start_time = get_current_time();
	message(STR_WAIT_A_WHILE); /* 「しばらくお待ちください\n」 */
	if (reserve_current_packet()) { /* never */
		DMESSAGE("resume_setup: internal error\n");
		return (long) -1;
	}
	buf = current_packet->body;

	fp = open_file("rb"); /* real_filename は設定されていること */
	if (fp == NULL) { /* can't open */
		return (long) -1;
	}

	n = fread(buf, 1, 1, fp);
	if (n == 0) { /* 中身がない */
		sts = ferror(fp);
		fclose(fp);
		if (sts) {
			message(STR_BAD_FILE); /* 「既存ファイルに異常があります\n"」 */
			return (long) -1;
		} else {
			message(STR_EMPTY_FILE); /* 「既存ファイル内容は空です\n"」 */
			return (long) 0;
		}
	}

	/* check value を計算、file size も求める */
	file_size = 1;
	check_value(CLEAR_CHECK_VALUE, buf[0]);

	while (length < 0 || (size = length - file_size) > 0) {
		if (length < 0 || size > MAX_PACKET_SIZE)
			size = MAX_PACKET_SIZE;
		n = fread(buf, 1, size, fp);
		if (n < 1) {
			break;
		}
		str_check_value(buf, n);
		file_size += n;

		now = get_current_time();
		if (now - start_time > 8)
			com_ssend("\020;");
	}

	/* 既存データは読み終わった */
	sts = ferror(fp);
	fclose(fp);
	if (sts) { /* read error */
		message(STR_BAD_FILE);
		return (long) -1;
	}

	return file_size;
}

/*--------------------------------------------------------------------*/
/* resume download
 * -1: fatal error, 処理中断すること
 * 0: resume はできないので、新規ダウンロードすること
 *  Bug!  I'm sorry, but you can't resume download a file which is larger
 *        than 2147483647 bytes..
 */
LOCAL long resume_start(UCHAR kind)
{
	UCHAR *buf; /* 作業用領域は空きパケットを利用 */
	long file_size;

	file_size = resume_setup((long) -1);
	if (file_size >= 0) {
		/* resume_setup が正常終了すれば、必ず current_packet に領域が確
		 * 保されているので、これを送信用に使う。
		 */
		buf = current_packet->body;

		/* 書き込みができることを確認すること。
		 * 書き込みができない場合、write protect の可能性がある。自動的
		 * にファイルのモードを変更するか、あるいは別ファイルに内容をコ
		 * ピーしてから、 overwrite する方法も考えられる。
		 */

		/* Tr packet を作成 */
		buf[0] = 'T';
		buf[1] = kind;
		if (kind == 'r') {
			add_ulong_to_str(file_size, buf + 2);
			add_ulong_to_str((ULONG) check_value(ASK_CHECK_VALUE, 0), NULL);
			if (send_current_packet((int) strlen(buf)) < 0) {
				file_size = (long) -1;
			} else {
				if (wait_ack(0, 0) < 0)
					file_size = (long) -1;
			}
		} else {
#ifdef WITH_INITIATOR
			UCHAR *s;

			buf[2] = 'B'; /* Binary only */
			s = buf + 3;
			add_ulong_to_str(file_size, s);
			add_ulong_to_str((ULONG) check_value(ASK_CHECK_VALUE, 0), NULL);
			strcat(s, real_filename);
			if (send_current_packet((int) strlen(buf)) < 0)
				file_size = (long) -1;
#endif
		}
	}
	return file_size;
}

#endif /* WITH_RESUME */

/*--------------------------------------------------------------------*/
static int td_filename()
{
	UCHAR *fname;

	/* TD パケットの内容でなく、直接ファイル名を指定したり、保存ディレク
	 * トリを指定する場合の処理を行う
	 */
	fname = (current_file == NULL) ? t_filename : (UCHAR *) current_file->name;
	if (create_real_filename(fname) < 0) {
		return -1;
	}

	/* 既存ファイルのある場合は、
	 *  このファイルを削除して新規作成する
	 *  別の名前のファイルを作成する
	 *  処理を中断する (-1)
	 *  もし DR > 0 なら、resume download を行う
	 * の、4 つから選択を行う。
	 */
	downmode = set_td_filename();
	return downmode < 0 ? -1 : 0;
}

/*--------------------------------------------------------------------*/
LOCAL UCHAR *look_buffer = NULL;
LOCAL UCHAR *look_current = NULL;
LOCAL UCHAR *look_end = NULL;

LOCAL void look_char(UCHAR ch)
{
	static UCHAR holdchar = 0;

#ifdef SJIS_TO_EUC
	if (ch == 0x0d)
		return;
#endif

	if (holdchar) {
#ifdef SJIS_TO_EUC
		int sjis_h, sjis_l, euc_h, euc_l;

		sjis_h = (int) holdchar;
		sjis_l = (int) ch;
		if (sjis_l > 0x9e) {
			euc_h = (sjis_h - ((sjis_h > 0x9f) ? 0xb0 : 0x70)) << 1;
			euc_l = sjis_l - 0x7e;
		} else {
			euc_h = ((sjis_h - (sjis_h > 0x9f ? 0xb1 : 0x71)) << 1) + 1;
			euc_l = sjis_l - 0x1f;
			if (sjis_l >= 0x80)
				euc_l--;
		}
		holdchar = (UCHAR) (euc_h | 0x80);
		ch = (UCHAR) (euc_l | 0x80);
#endif
		*look_current++ = holdchar;
		holdchar = 0;
	} else {
#ifdef SJIS_TO_EUC
		if (isMSkanji1st(ch)) {
			holdchar = ch;
			return;
		}
		if (ch > 0x7f) {
			*look_current++ = 0x8e;
		}
#else
		if (iskanji1st(ch)) {
			holdchar = ch;
			return;
		}
#endif
	}
	*look_current++ = ch;
	if (ch == '\n' || look_current >= look_end) {
		*look_current = '\0';
		strout((char *) look_buffer);
		look_current = look_buffer;
	}
}

/*--------------------------------------------------------------------*/
/* packet の中身を表示する
 */
LOCAL void look_packet(int size, UCHAR *s)
{
	if (look_buffer != NULL) {
		charout('\n');
		while (size-- > 0) {
			look_char(*s++);
		}
		charout('\n');
	}
}

/*--------------------------------------------------------------------*/
/* download 処理。この処理中では、N, T, F, 以外のパケットは受け取ること
 * ができない。
 * センター -> 端末 TD パケット (この関数呼び出し直前の状態)
 * resume 処理でない場合は
 *    端末 -> センター ACK
 *    センター -> 端末 N パケット…
 * resume 処理の場合は、
 *    端末 -> センター Tr パケット
 *    a) センター -> 端末 Fr パケットなら、処理中断
 *    b) センター -> 端末 Tf パケットなら、最初から送信してくる
 *       端末 -> センター ACK
 *    センター -> 端末 N パケット…
 */
static int td_packet(PACKET *pac)
{
	FILE *fp;
	ULONG recvsize = (ULONG) 0;
	ULONG start_size;
	ULONG start_time;
	ULONG filesize = 0;
	int n;
	int count;
	int size;
	int sts = 0;

	/* ファイル名を作成し、既存ファイルの有無、overwrite 確認等 */
	if (td_filename() < 0) {
		goto error_case;
	}

#ifdef WITH_INITIATOR
	if (pac == NULL) {
		if (downmode == DOWNMODE_RESUME) {
#ifdef WITH_RESUME
			/* upload resume 開始。terminal の応答は 3 通り考えられる。
			 * 1: CRC が一致した場合…ただちに最初の packet を送ってくる
			 *    resume_upload_start() は正の値を戻す
			 * 2: Fr を返してくる場合
			 *    resume_upload_start() は負の値を戻す
			 * 3: Tf を返してくる場合
			 *   この場合は、内容が異なっているので、新規作成になる。Tf に対
			 *   する ACK を送れば、terminal から最初の packet が送られてくる。
			 *    resume_upload_start() は 0 を戻す
			 */
			recvsize = resume_start((UCHAR) 'u');
			if (recvsize < 0) { /* F packet 送信済 */
				return -1;
			}
#endif /* WITH_RESUME */
		} else {
			sts = send_tx_packet("TUB");
		}
		sts = wait_ack(0, 1); /* ACK かデータがあるまで待つ */
	} else {
#endif /* WITH_INITIATOR */

#ifdef WITH_RESUME
		if (downmode == DOWNMODE_RESUME) {
			recvsize = resume_start((UCHAR) 'r'); /* Tr packet を送信する */
			if (recvsize < 0) { /* F packet 送信済 */
				return -1;
			}
		}
#endif /* WITH_RESUME */

#ifdef WITH_INITIATOR
	}
#endif

	fp = open_file(downmode ? "ab" : "wb");
	if (fp == NULL) {
		goto error_case;
	}

	/* TD packet に対する ACK を送信する。resume download の場合は、Tr packet
	 * 処理時に ACK を送っているので、スキップする
	 */
#ifdef WITH_INITIATOR
	if (pac != NULL)
#endif
#ifdef WITH_RESUME
	if (downmode != DOWNMODE_RESUME)
#endif
		send_ack(pac);

	count = 0;
	mes_filename();
	start_time = get_current_time();
	start_size = recvsize; /* 速度計算用 */
	do {
		if (sts == 0)
			pac = read_packet(S_Get_DLE);
		if (pac == NULL) {
			message(STR_INTERRUPT); /* 「処理を中断します\n」 */
			break;
		}
		switch (pac->body[0]) {
		case 'N':
			size = pac->size - 1;
			com_rts_off();
			n = fwrite(pac->body + 1, 1, size, fp);
			com_rts_on();
			if (n < size) {
				/* 「書き込み失敗です。処理を中断します\n」 */
				message(STR_WRITE_FAILURE);
				send_failure_packet("EWrite failure");
				sts = -1;
			} else {
				if (look_text && ti_information.data_type == 'A') {
					look_packet(size, pac->body + 1);
				}
				send_ack(pac);
				recvsize += (ULONG) n;
				if (filesize)
					mes_datasize(filesize, recvsize, count);
				sts = 0; /* 901106 */
			}
			count++;
			break;

		case 'T':
			/* ACK, F packet 処理は transfer() で行う */
			sts = transfer(pac, FROM_DOWNLOAD, fp);
			if (sts == 1) { /* normal end */
				/* TC packet を受け取り、fp は close されている */
				mes_speed(recvsize - start_size,
					get_current_time() - start_time);
				return 1;
			} else if (sts == 2) { /* TI packet */
				filesize = ti_information.info[0];
				mes_filesize(filesize);
				sts = 0;
			} else if (sts == 3) { /* Tf packet */
#ifdef WITH_RESUME
				/* 「内容が合致しません。新規作成します。\n」 */
				fclose(fp);
				fp = NULL;
				message(STR_NOT_MATCHED);
				if (want_to_overwrite()) { /* 旧ファイルは破壊される */
					fp = open_file("wb");
				}
				if (fp == NULL) {
					fp = open_tmpfile();
					if (fp == NULL)
						goto error_case;
				}
				recvsize = 0;
				sts = 0;
				downmode = DOWNMODE_NORMAL;
				send_ack(pac);
#endif
			}
			/* sts < 0 の場合には、既に failure packet は送られている */
			break;

		case 'F':
			send_ack(pac);
			message(STR_GET_FPACKET); /* 「中止要求を受け取りました」 */
			sts = -1;
			break;

		default:
			send_failure_packet("NUnknown packet");
			sts = -1; /* unknown */
			break;
		}
	} while (sts == 0);

	fclose(fp);
	decide_to_delete();
	return -1;

error_case:
	send_failure_packet("CCannot create file");
	return -1;
}

/*--------------------------------------------------------------------*/
/* ファイルのサイズを求める。ファイルポインタは呼びだした位置から移動し
 * ない。
 */
LOCAL ULONG get_filesize(FILE *fp)
{
	ULONG len;
	ULONG pos;

	pos = ftell(fp);
	fseek(fp, 0L, 2);
	len = ftell(fp);
	fseek(fp, pos, 0);
	return len;
}

/*--------------------------------------------------------------------*/
/*	ファイル名の再入力。キャンセルは何も入力せずにリターンすれば NULL
 *	を戻す。
 */
LOCAL FILE *reenter_filename()
{
	FILE	*fp;
	char	buf[83];

	do {
		if (ask_filename(buf))
			return NULL;
		if (create_real_filename((UCHAR *) buf) < 0)
			fp = NULL;
		else {
			fp = fopen(real_filename, "rb");
		}
		if (fp == NULL) {
			cannot_open_message();
		}
	} while (fp == NULL);

	return fp;
}

/*--------------------------------------------------------------------*/
LOCAL void create_file_information()
{
	int sts;

	ti_information.valid = 0;
	sts = get_file_information((char *) real_filename, ti_information.info);
	if (sts == 0) {
		int length;
		char *s;

		ti_information.data_type = file_type;
		ti_information.compression = 0;
		length = strlen(real_filename);
		s = (char *)(ti_information.true_name = (UCHAR *) malloc(length + 2));
		if (s != NULL) {
			*s++ = (char) length;
			strcpy(s, real_filename);
			ti_information.valid = 1;
		}
	}
}

/*--------------------------------------------------------------------*/
/* ファイルをオープンする。
 * append mode の時には、指定されているファイル名を順次使ってオープン
 * オープンできない場合には、メッセージを表示して NULL を戻す。
 */
STATIC FILE *open_tu_file(int atfirst)
{
	FILE *fp;
	UCHAR *fname;
	int at_first_time = atfirst;
	int sts;
	static int second_time;

	do {
		do {
			if (atfirst) {
				fname = t_filename;
				if (!append_mode) {
					if (current_file != NULL)
						fname = (UCHAR *) current_file->name;
				}
#ifdef WITH_INITIATOR
				/* ファイルが指定されなかった */
				if (fname == NULL)
					return NULL;
#endif
				second_time = 1;
				atfirst = 0;
			} else {
				if (!second_time) {
					if (current_file != NULL)
						current_file = current_file->next;
				}
				second_time = 0;
				if (current_file == NULL) {
					/* append mode 最後だからメッセージは不要 */
					return NULL;
				}
				fname = (UCHAR *) current_file->name;
			}
			sts = create_real_filename(fname);
			if (sts) {
				if (!append_mode) {
					/* 「ファイル名の処理中に異常がありました\n」 */
					message(STR_FILENAME_TROUBLE);
					return NULL;
				}
			}
		} while (sts);

		if (at_first_time)
			create_file_information();
		fp = fopen(real_filename, "rb");
		if (fp == NULL) { /* ファイルがない */
			cannot_open_message();
			if (!append_mode)
				return reenter_filename();
		}
	} while (fp == NULL);

	return fp;
}

/*--------------------------------------------------------------------*/
/* upload 処理
 * センター -> 端末 Tu
 *    実行可能なら、端末 -> センター N パケット…
 *    実行不可能、または UR=1 なら、端末 -> センター Fr パケットで中断
 *    CRC 不一致で最初から送れるなら、端末 -> センター Tf パケット
 *    センター -> 端末 ACK
 *    端末 -> センター N パケット
 */
STATIC int tu_packet(PACKET *pac, FILE *fp)
{
	ULONG filesize;
	ULONG sendsize;
	ULONG startsize;
	ULONG start_time;
	int block_byte;
	int count;
	int n;
	int sts;

	if (fp == NULL) {
		fp = open_tu_file(1); /* 初回のみ 1 */
		if (fp == NULL) {
			send_failure_packet("MFile not found");
			return -1;
		}
	}

	filesize = get_filesize(fp);
	sendsize = ftell(fp);
	count = 0;
	block_byte = 128 * block_size;
	if (pac != NULL) {
		send_ack(pac);
		packet_ack = packet_no; /* ? */
	}

	mes_filename();
	mes_filesize(filesize);

	if (file_information > 0 && ti_information.valid) {
		sts = send_ti_packet();
		if (sts < 0)
			goto FAILED;
	}

	start_time = get_current_time();
	startsize = sendsize;
	for (;;) {
		sts = reserve_current_packet();
		if (sts) /* fatal .. never */
			break;

		current_packet->body[0] = 'N';
		n = fread(current_packet->body + 1, 1, block_byte, fp);
		if (n == 0) {
			sts = ferror(fp);
			if (sts != 0) {
				send_failure_packet("EFile read failure");
				/* 「ファイルの読み込み中に異常がありました\n」 */
				message(STR_READ_TROUBLE);
				break;
			}

			if (!append_mode) { /* 処理終了 */
				break;
			}

			fp = open_tu_file(0);
			if (fp == NULL)
				break;
			mes_filename();
			filesize += get_filesize(fp);
			mes_filesize(filesize);
		} else { /* n > 0 */
			sts = send_current_packet(n + 1);
			if (sts < 0) {
				/* F packet は送られてないはずだが */
				send_failure_packet("ATransfer cancelled");
				break;
			}
			sendsize += (ULONG) n;
			count++;
			mes_datasize(filesize, sendsize, count);
		}
	}

FAILED:
	if (fp != NULL) {
		fclose(fp);
	}

	if (sts == 0) {
		sts = send_tx_packet("TC");
		if (sts == 0) {
			sts = wait_ack(0, 0);
		}
		message("\n");
		mes_speed(filesize - startsize, get_current_time() - start_time);
	}

	return sts == 0 ? 1 : sts;
}

/*--------------------------------------------------------------------*/
LOCAL void date_time(ULONG d, ULONG t)
{
	ldecout(d / 10000L);
	charout('/');
	d %= 10000L;
	ldecout(d / 100);
	charout('/');
	ldecout(d % 100);
	charout(' ');
	showtime(t);
	strout("\n");
}

/*--------------------------------------------------------------------*/
LOCAL void ti_message()
{
	char *s;

	if (verbose > 1) {
		strout(STR_FILE_INFO); /* 「*** ファイル情報 ***\n」 */
		strout(STR_DATA_TYPE); /* 「o データ形式: 」 */
		switch (ti_information.data_type) {
		case 'A':
			s = STR_TEXT; /* 「テキスト」 */
			break;
		case 'B':
			s = STR_BINARY; /* 「バイナリ」 */
			break;
		case 'I':
			s = STR_PICTURE; /* 「画像」 */
			break;
		default:
			s = STR_UNKNOWN_TYPE; /* 「(不明)」 */
			break;
		}
		strout(s);
		strout("\n");
		if (ti_information.compression) {
			strout(STR_COMPRESSION); /* 「o データ圧縮\n」 */
		}
		strout(STR_FILESIZE); /* 「o ファイルサイズ: 」 */
		ldecout(ti_information.info[0]);
		strout(STR_BYTES); /* 「 bytes\n」 */
		if (verbose > 1) {
			strout(STR_TIMEZONE); /* 「o タイムゾーン: 」 */
			decout(ti_information.info[1]);
			strout("\n");
		}
		if (ti_information.info[2]) {
			strout(STR_CREATE_TIME); /* 「o ファイル作成日時: 」 */
			date_time(ti_information.info[2], ti_information.info[3]);
		}
		if (ti_information.info[4]) {
			strout(STR_MODIFY_TIME); /* 「o ファイル変更日時: 」 */
			date_time(ti_information.info[4], ti_information.info[5]);
		}
		if (ti_information.true_name != NULL) {
			strout(STR_TI_FILENAME); /* 「o ファイル名: 」 */
			strout((char *) ti_information.true_name + 1);
			strout("\n");
		}
	}
}

/*--------------------------------------------------------------------*/
/* TI packet (pac) の情報から、FILE_INFO 形式のデータを生成し、
 * ti_informaition へ格納する。
 */
LOCAL int ti_packet(PACKET *pac)
{
	UCHAR *s;
	int i;
	int length;

	scan_init(pac, 2);
	ti_information.valid = 0;
	ti_information.data_type = scan_byte();
	ti_information.compression = scan_byte();
	for (i = 0; i < 6; i++) {
		ti_information.info[i] = scan_number();
	}
	length = scan_byte();
	if (scan_error == 0) {
		ti_information.true_name = s = (UCHAR *) malloc(length + 2);
		if (s != NULL) {
			*s++ = (char) length;
			while (length-- > 0) {
				*s++ = (UCHAR) scan_byte();
			}
			*s = 0;
		}
	}
	if (scan_error)
		return -1;
	ti_information.valid = 1;
	ti_message();
	return 0;
}

/*--------------------------------------------------------------------*/
/* 正常終了の場合には 1、エラーの場合には -1 を戻す。
 */
static int tc_packet(PACKET *pac, FILE *fp)
{
	int sts;

	message(STR_TRANSFER_COMPLETE); /* 「\n−転送終了−\n」 */
	sts = ferror(fp);
	fclose(fp);
	if (sts) {
		message(STR_CANT_CLOSE); /* 「ファイルをクローズできません\n」 */
		send_failure_packet("EError during close");
		return -1;
	}
	send_ack(pac);
	if (ti_information.valid && set_ftime) {
		set_file_time((char *) real_filename, ti_information.info);
	}
	return 1;
}

/*--------------------------------------------------------------------*/
/* T packet からファイル名を取り出し、t_filename に格納。
 */
LOCAL int set_t_filename(UCHAR *s, int len)
{
	if (len <= 0)
		return -1;
	my_free(t_filename);
	t_filename = NULL;
	t_filename = (UCHAR *) malloc(len + 1);
	if (t_filename == NULL)
		return -1;
	memcpy(t_filename, (char *) s, len);
	t_filename[len] = 0;
	DMESSAGE("set_t_filename %s\n", t_filename);

	return 0;
}

#ifdef WITH_RESUME
/*--------------------------------------------------------------------*/
/* upload resume (TU) / download resume (TD) で CRC エラーまたは
 * file length 異常があった場合に呼ばれる。処理中断なら -1、最初から処理
 * するなら 0 を戻す。
 * mode に upload_resume or download_resume の値を入れて呼び出す。
 */
LOCAL int resume_packet_failed(int mode)
{
	int sts;

	if (mode == 2) {
		sts = send_tx_packet("Tf");
		if (sts == 0)
			sts = wait_ack(0, 0);
	} else { /* mode == 1 */
		send_tx_packet("Fr");
		sts = -1;
	}
	return sts;
}

/*--------------------------------------------------------------------*/
LOCAL int upload_resume_packet(PACKET *pac)
{
	FILE *fp;
	int crc;
	long length;

	/* この ack は必要か?? */
	send_ack(pac);

	/* Data Type は無視される */
	scan_init(pac, 3);
	length = scan_number();
	crc = (int) scan_number();
	DMESSAGE("upload_resume_packet: length = %ld, crc = %x\n", length, crc);

	/* file name 処理 */
	while (*scan_ptr == ' ')
		scan_ptr++;
	if (scan_error || set_t_filename(scan_ptr, scan_end - scan_ptr - 3) < 0) {
		/* something fatal */
		send_failure_packet("MFile not found");
		return -1;
	}
	fp = open_tu_file(1);
	if (fp == NULL)
		return -1;

	length = resume_setup(length);
	if (length < 0 || crc != check_value(ASK_CHECK_VALUE, 0)) {
		/* resume できない。最初から送ることになる。 */
		if (resume_packet_failed(upload_resume) < 0) {
			/* upload_resume の状態に従って Tf または Fr パケットを送り、
			 * 最初から送信可能なら 0 以上、不可なら負の値が戻ってくる。
			 */
			return -1;
		}
		length = 0;
	}

	fp = open_file("rb");
	if (fp == NULL)
		return -1;

	fseek(fp, length, 0);
	return tu_packet(NULL, fp);
}
#endif /* WITH_RESUME */

/*--------------------------------------------------------------------*/
/* T パケット処理
 * 処理に異常があれば、この関数を出る時点で Failure Packet が送られてい
 * る。正常なら、ACK が送られている。
 */
static int transfer(PACKET *pac, int mode, FILE *fp)
{
	UCHAR type;
	int sts = -1;

	DMESSAGE("transfer\n");
	type = pac->body[1];
	if (mode == FROM_TOPLEVEL && (type == 'D' || type == 'U')) {
		ti_information.valid = 0;
		/* ファイル名を変数 t_filename にセットする。
		 */
		sts = set_t_filename(pac->body + 3, pac->size - 3);
		if (sts < 0)
			return sts;
	}

	switch (type) {
	case 'D': /* Download */
		if (mode != FROM_TOPLEVEL)
			goto invalid;
		sts = td_packet(pac);
		break;

	case 'U': /* Upload */
		if (mode != FROM_TOPLEVEL)
			goto invalid;
		sts = tu_packet(pac, NULL);
		break;

	case 'C': /* Close */
		if (mode == FROM_TOPLEVEL)
			goto invalid;
		sts = tc_packet(pac, fp);
		break;

	case 'I': /* Information */
		sts = ti_packet(pac);
		send_ack(pac); /* TI packet の異常は無視する */
		sts = 2; /* 戻値は 2 */
		break;

#ifdef WITH_RESUME
	/* Upload Resume、FROM_TOPLEVEL で構わないかどうか、検討が必要 */
	case 'u':
		if (mode != FROM_TOPLEVEL)
			goto invalid;
		sts = upload_resume_packet(pac);
		break;

	case 'f': /* Tf .. resume failed */
		if (mode == FROM_TOPLEVEL)
			goto invalid;
		sts = 3;
		break;
#endif

	default: /* unknown */
invalid:
		/* 「当バージョンでは処理できないパケットがありました\n」 */
		message(STR_CANT_PROCESS);
		send_failure_packet("NInvalid T Packet");
		sts = -1;
		break;
	}
	return sts;
}

/*===== Setup mode ===================================================*/
static void reset_normal()
{
	block_size = 512 / 128;
	set_quoting_level(1); /* Default B+ */
	check_method = CM_IS_CHECKSUM;
	packet_no = packet_ack = '0';
}

/*--------------------------------------------------------------------*/
LOCAL void show_defaults(UCHAR *s, int size)
{
	int i;
	static char *table [] = {
		"WS=", ", WR=", ", BS=", ", CM=", ", DQ=", ", TL=",
		"\nQ1=", ", Q2=", ", Q3=", ", Q4=", ", Q5=", ", Q6=",
		", Q7=", ", Q8=", "\nDR=", ", UR=", ", FI="
	};

	for (i = 0; i < size; i++) {
		strout(table[i]);
		decout((int) *s++);
	}
	charout('\n');
}

/*--------------------------------------------------------------------*/
STATIC int set_our_defaults(PACKET *pac)
{
	UCHAR *param;
	int i;
	int size;
	int use_quote_set;

	size = pac->size - 1;
	if (size > sizeof(our_defaults))
		size = sizeof(our_defaults);
	param = pac->body + 1; /* pac->body[0] == '+' */
	if (verbose > 1)
		show_defaults(param, size);

	memcpy(our_defaults, default_parameters, sizeof(our_defaults));
	use_quote_set = 0;
	for (i = 0; i < size; i++) {
		if (i >= 6 && i <= 13) {
			our_defaults[i] = my_defaults[i] | param[i];
		} else {
			our_defaults[i] =
				my_defaults[i] < param[i] ?
				my_defaults[i] :
				param[i];
		}
	}

	if (size < 13) {
		use_quote_set = 1;
	}

	/* Window Send と Window Receive は、入れ替える */
	window_send = our_defaults[1];
	window_receive = our_defaults[0];
	our_defaults[0] = window_send;
	our_defaults[1] = window_receive;

	block_size = our_defaults[2];
	if (block_size == 0) {
		our_defaults[2] = block_size = 512/128;
	}
	check_method = our_defaults[3];
	transport_layer = our_defaults[5];
	if (use_quote_set == 0) {
#ifdef WITH_RESUME
		download_resume = our_defaults[14];
		upload_resume = our_defaults[15];
#endif
		file_information = our_defaults[16];
	}

	return use_quote_set;
}

/*--------------------------------------------------------------------*/
static int set_to_bplus(PACKET *pac)
{
	int sts;
	int default_sts;

	default_sts = set_our_defaults(pac);
	check_method = CM_IS_CHECKSUM; /* for next packet only */
	set_quoting_level(3);

	/* + パケットを送信する */
	send_ack(pac);
	if (reserve_current_packet())
		return -1;
	current_packet->body[0] = '+';
	memcpy(current_packet->body + 1, our_defaults, 17);

	sts = send_current_packet(18);
	check_method = our_defaults[3];
	if (sts == 0) {
		sts = wait_ack(0, 0);
	}
	if (default_sts == 0)
		set_our_quotetable(our_defaults + 6);
	else
		set_quoting_level(our_defaults[4]);
	return sts;
}

/*===== Terminal emulation ===========================================*/
/* ターミナル状態 (Terminal Program Status)
 */
#define	TPS_Terminal 0
#define	TPS_DLE_Seen 1
#define	TPS_Get_First_Packet 2

LOCAL int terminal_program_status = TPS_Terminal;
LOCAL char init_string[6] = {DLE, '+', '+', DLE, '0', 0};

/*--------------------------------------------------------------------*/
/* <Sequence> <Type> <Body> <Trailer> を読み込む。
 */
LOCAL PACKET *get_packet_after_leadin()
{
	if (reserve_current_packet() < 0) { /* バッファが全て使われている !? */
		return NULL;
	}

	return read_packet(S_DLE_B_Seen);
}

/*--------------------------------------------------------------------*/
/* XXX -- Other model/type
 * CA -- ANSI/VT100 Cursor Control
 * SS7o -- 80x24 screen,
 * PB -- Use B protocol
 */
LOCAL char response[] = "#XXX,CA,SS7o,PB,+";

LOCAL void answer_esc_i()
{
	char tmp[16];
	int checksum = 0;
	int i;

	for (i = 0; i < sizeof(response); i++) {
		com_send(response[i]);
		checksum += response[i];
	}
	/* 4 桁になるように、先頭は '0' で pad する。このために、10000 を追加
	 * しておき、万の桁の次からを送信している。
	 */
	ulong_to_str((ULONG) 10000 + checksum, (UCHAR *) tmp);
	com_ssend(tmp + 1);
	com_send(0x0d);
	return;
}

/*--------------------------------------------------------------------*/
LOCAL int terminal()
{
	int c;
	int sts = 0;

	c = bp_com_recv();
	switch (c) {
	/* 次のデータが ENQ なら、normal B プロトコルの設定にリセットた上で、
	 * DLE + + DLE 0 を送信する。状態は TPS_Terminal のまま。
	 */
	case ENQ:
		reset_normal();
		com_ssend(init_string);
		break;

	/* 次のデータが DLE なら、dle_seen() を実行
	 */
	case DLE:
		terminal_program_status = TPS_DLE_Seen;
		break;

	case ESC:
		c = bp_com_recv();
		if (c == 'I') {
			answer_esc_i();
			break;
		}
		charout(ESC);
		/* fall into default */

	/* それ以外のデータは、そのまま画面に表示する。
	 */
	default:
		if (c >= -256 && c < 0) { /* キーが押された */
			sts = -2;
		} else if (c >= 0) {
			charout(c);
		}
		break;
	}
	return sts;
}

/*--------------------------------------------------------------------*/
LOCAL int dle_seen()
{
	int c;
	int sts = 0;

	c = bp_com_recv();
	if (c == 'B') { /* DLE B でパケット開始であることの確認となる */
		sts = 0;
		terminal_program_status = TPS_Get_First_Packet;
	} else {
		if (c < 0) { /* Timeout or Error (Cancel) */
			sts = -2;
		}
		terminal_program_status = TPS_Terminal;
	}
	return sts;
}

/*--------------------------------------------------------------------*/
/* terminal mode で受け取ったパケットの処理
 */
LOCAL int get_first_packet()
{
	PACKET *pac;
	int sts;

	/* 次の状態は必ず TPS_Terminal */
	terminal_program_status = TPS_Terminal;

	pac = get_packet_after_leadin();

	if (pac == NULL) { /* error - Failure Packet N を送信 */
#if 0
		send_failure_packet("NFatal error");
#endif
		sts = 0;
	} else {
		switch (pac->body[0]) {
		case '+':
			sts = set_to_bplus(pac);
			break;

		case 'T':
			sts = transfer(pac, FROM_TOPLEVEL, NULL);
			break;

		default:
			send_failure_packet("NNot implemented");
			sts = 0; /* 処理できないパケットは無視する */
			break;
		}
		DMESSAGE("get_first_packet: type = %c\n", pac->body[0]);
	}

	return sts;
}

/*--------------------------------------------------------------------*/
LOCAL int start_terminal(APPL_PARAM *param)
{
	/* static 変数にパラメータ状態設定 */
	force = param->flags & FLAGS_FORCE;
	file_type = param->flags & FLAGS_BINARY ? 'B' : 'A';
	set_ftime = param->flags & FLAGS_SETFTIME;
	verbose = param->verbose & 3;
	look_text = param->verbose & 4;
	append_mode = param->append_mode;
	estimate_interval = param->est_interval;
	dirnames = param->dirname;
	current_file = param->fnlist;
	my_defaults[2] = param->my_bps;
#ifdef WITH_RESUME
	if (param->flags & FLAGS_RESUME) {
		my_defaults[14] = download_resume = 2;
		my_defaults[15] = upload_resume = 2;
	}
#endif
	ti_information.valid = 0;

#ifdef BUGMODEM
	try_to_fly = param->flags & FLAGS_FLY;
	flying = 0;
#endif

	/* packet を保存する領域の獲得 */
	packets = (PACKET *) malloc(sizeof(PACKET) * PACKET_NUM);
	if (packets == NULL)
		return -1;
	init_all_packets();

	look_buffer = (UCHAR *) malloc(MAX_PACKET_SIZE);
	if (look_buffer != NULL) {
		look_current = look_buffer;
		look_end = look_buffer + MAX_PACKET_SIZE - 4;
	}
	return 0;
}

/*--------------------------------------------------------------------*/
LOCAL void end_terminal(int sts)
{
	if ((sts < 0) && (failure_status == 0)) {
		send_failure_packet("ATransfer cancelled");
	}
	if (failure_status) {
		do {
			while (com_trecv(2) >= 0)
				;
			com_send('Q'-'@');
		} while (com_trecv(2) >= 0);
	}

	/* packet 保存用のバッファを解放する */
	my_free(packets);
	packets = NULL;
	my_free(real_filename);
	real_filename = NULL;
	my_free(t_filename);
	t_filename = NULL;
	my_free(ti_information.true_name);
	ti_information.true_name = NULL;
	my_free(look_buffer);
	look_buffer = NULL;
}

/*--------------------------------------------------------------------*/
#ifdef DEBUG
static void print_parameters(APPL_PARAM *param)
{
	printf("force: %d\n", param->flags & FLAGS_FORCE);
	printf("verbose: %d\n", param->verbose);
	printf("append_mode: %d\n", param->append_mode);
	printf("skipenq: %d\n", param->flags & FLAGS_SKIPENQ);
	printf("only_once: %d\n", param->flags & FLAGS_ONLY_ONCE);
	printf("my_bps: %d\n", param->my_bps);
	printf("maskchar: %d\n", param->maskchar);
	printf("fly: %d\n", param->flags & FLAGS_FLY);
	printf("dirname: %s\n", param->dirname ? (char *) param->dirname : "none");
	printf("fnlist: %s\n", param->dirname ? "exists" : "none");
#ifdef WITH_INITIATOR
	printf("initiator: %d\n", param->flags & FLAGS_INITIATOR);
#endif
}
#endif

#ifdef WITH_INITIATOR
/*--------------------------------------------------------------------*/
LOCAL int send_plus_packet()
{
	PACKET *pac;
	int sts;

	set_quoting_level(3);
	if (reserve_current_packet() < 0)
		return -1;

	current_packet->body[0] = '+';
	memcpy(current_packet->body + 1, my_defaults, 17);
	sts = send_current_packet(18);
	if (sts == 0) {
		sts = wait_ack(0, 0);
	}
	if (sts)
		return sts;

	pac = read_packet(S_Get_DLE);
	if (pac == NULL)
		return -1;
	set_our_defaults(pac);
	set_our_quotetable(our_defaults + 6);
	send_ack(pac);
	DMESSAGE("packet_ack = %c, packet_no = %c\n", packet_ack, packet_no);
	return 256; /* normal end */
}

/*--------------------------------------------------------------------*/
LOCAL int start_initiator()
{
	int i;
	int sts;

	for (;;) {
		sts = com_send(ENQ);
		if (sts < 0)
			return sts;

		do {
			sts = bp_com_recv();
		} while (sts >= 0 && sts != DLE);

		if (sts == TIMEOUT)
			continue;

		if (sts < 0)
			return sts;

		for (i = 1; i < 5; i++) {
			sts = bp_com_recv();
			if (sts != init_string[i])
				break;
		}
		if (i == 5) {
			sts = send_plus_packet();
			if (sts == 256)
				break;
		}
	}

	return sts == 256 ? 0 : -1;
}

#ifdef WITH_RESUME
/*--------------------------------------------------------------------*/
/* Initiator の Tr パケット処理。
 */
LOCAL long download_resume_packet(PACKET *pac)
{
	ULONG file_size; /* 実際に読み込んだファイルの大きさ */
	ULONG length; /* スキップするサイズ */
	int crc;

	scan_init(pac, 2); /* "Tr" をスキップする */
	length = scan_number();
	crc = (int) scan_number();

	if (scan_error || (length == 0)) {
		return (long) -1;
	}
	DMESSAGE("length = %ld\n", length);
	file_size = resume_setup(length);
	if (file_size < 0 || crc != check_value(ASK_CHECK_VALUE, 0)) {
		return (long) resume_packet_failed(download_resume);
	}
	return file_size;
}
#endif

/*--------------------------------------------------------------------*/
/* TD packet を送信後、ACK ではなく Tr packet が戻ってくると、resume
 * download を行う。
 */
LOCAL int download_start()
{
	int sts;
	int td_sequence;
	FILE *fp;

	fp = open_tu_file(1); /* 指定ファイルがオープンできることの確認 */
	if (fp == NULL) {
		message(STR_BAD_FILENAME);
		return -1;
	}
	fclose(fp);
	fp = NULL;

	sts = send_tx_packet("TDB");
	if (sts < 0)
		return sts;
	td_sequence = current_packet->sequence;

#ifdef WITH_RESUME
	sts = wait_ack(0, 1);

	if (sts < 0)
		return sts;

	if (sts > 0) {
		PACKET *pac;

		pac = current_packet;
		if (pac->body[0] == 'T' && pac->body[1] == 'r') {
			long size = (long) 0;

			/* TU パケットに対して Tr パケットが戻ってきた場合、TU パケッ
			 * トへの ACK としてカウントする。
			 */
			free_packet(td_sequence);

			/* pac の ack をここで送ってしまう。ただし、pac の中味は直後の
			 * download_resume_packet() 呼び出しで使われていることに注意
			 */
			send_ack(pac);

			size = download_resume_packet(pac);
			if (size < (long) 0) {
				return -1;
			}
			fp = open_file("rb");
			if (fp == NULL) {
				return -1;
			}
			fseek(fp, size, 0);
		}
	}

#else

	sts = wait_ack(0, 0);

#endif /* WITH_RESUME */

	sts = tu_packet(NULL, fp);
	return sts;
}

/*--------------------------------------------------------------------*/
int simulate_initiator(APPL_PARAM *param)
{
	int sts; /* 負の時は異常、正なら正常 */

	do {
		sts = start_initiator();
		if (sts < 0)
			break;
		sts = param->flags & FLAGS_UPLOAD ? td_packet(NULL) : download_start();
	} while (sts == 0 || ((sts > 0) && (param->flags & FLAGS_ONLY_ONCE == 0)));

	end_terminal(sts);

	return sts > 0 ? 0 : sts;
}
#endif

/*--------------------------------------------------------------------*/
/* 通信処理
 */
int execute_bpl_session(APPL_PARAM *param)
{
	int sts; /* 負の時は異常、正なら正常 */

#ifdef DEBUG
	print_parameters(param);
#endif

	if (start_terminal(param) < 0) { /* communication port 初期設定等 */
		return -1; /* out of memory */
	}

#ifdef WITH_INITIATOR
	if (param->flags & FLAGS_INITIATOR) {
		return simulate_initiator(param);
	}
#endif

	terminal_program_status = TPS_Terminal;
	reset_normal();
	if (param->flags & FLAGS_SKIPENQ) {
		com_ssend(init_string);
	}

	do {
		switch (terminal_program_status) {
		case TPS_Terminal:
			sts = terminal();
			break;

		case TPS_DLE_Seen:
			sts = dle_seen();
			break;

		case TPS_Get_First_Packet:
			sts = get_first_packet();
			break;
		}
	} while (sts == 0 || ((sts > 0) && (param->flags & FLAGS_ONLY_ONCE == 0)));

	end_terminal(sts);

	return sts > 0 ? 0 : sts;
}

/*===== Written by Phinloda, 1990, 1991 ==============================*/
