/*
 * Copyright (C) 1994-1998 T. Teranishi
 * (C) 2005- TeraTerm Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/* IPv6 modification is Copyright (C) 2000, 2001 Jun-ya KATO <kato@win6.jp> */

/* TERATERM.EXE, Communication routines */
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h> /* for _snprintf() */
#include <time.h>
#include <process.h>

#include "teraterm.h"
#include "tttypes.h"
#include "tt_res.h"
#include "ttcommon.h"
#include "ttwsk.h"
#include "ttlib.h"
#include "ttfileio.h"
#include "ttplug.h" /* TTPLUG */
#include "ttdde.h"
#include "commlib.h"
#include "filesys_log.h"
#include "ttlib.h"
#include "codeconv.h"
#include "helpid.h"
#include "vtwin.h"

static SOCKET OpenSocket(PComVar);
static void AsyncConnect(PComVar);
static int CloseSocket(SOCKET);

/* create socket */
static SOCKET OpenSocket(PComVar cv)
{
	cv->s = cv->res->ai_family;
	cv->s = Psocket(cv->res->ai_family, cv->res->ai_socktype, cv->res->ai_protocol);
	return cv->s;
}

/* connect with asynchronous mode */
static void AsyncConnect(PComVar cv)
{
	int Err;
	BOOL BBuf;
	BBuf = TRUE;
	/* set synchronous mode */
	PWSAAsyncSelect(cv->s,cv->HWin,0,0);
	Psetsockopt(cv->s,(int)SOL_SOCKET,SO_OOBINLINE,(char *)&BBuf,sizeof(BBuf));
	/* set asynchronous mode */
	PWSAAsyncSelect(cv->s,cv->HWin,WM_USER_COMMOPEN, FD_CONNECT);

	// ホストへの接続中に一定時間立つと、強制的にソケットをクローズして、
	// 接続処理をキャンセルさせる。値が0の場合は何もしない。
	// (2007.1.11 yutaka)
	if (*cv->ConnetingTimeout > 0) {
		SetTimer(cv->HWin, IdCancelConnectTimer, *cv->ConnetingTimeout * 1000, NULL);
	}

	/* WM_USER_COMMOPEN occurs, CommOpen is called, then CommStart is called */
	Err = Pconnect(cv->s, cv->res->ai_addr, cv->res->ai_addrlen);
	if (Err != 0) {
		Err = PWSAGetLastError();
		if (Err == WSAEWOULDBLOCK)  {
			/* Do nothing */
		} else if (Err!=0 ) {
			PostMessage(cv->HWin, WM_USER_COMMOPEN,0,
			            MAKELONG(FD_CONNECT,Err));
		}
	}
}

/* close socket */
static int CloseSocket(SOCKET s)
{
	return Pclosesocket(s);
}

#define CommInQueSize 8192
#define CommOutQueSize 2048
#define CommXonLim 2048
#define CommXoffLim 2048

#define READENDNAME "ReadEnd"
#define WRITENAME "Write"
#define READNAME "Read"
#define PRNWRITENAME "PrnWrite"

static HANDLE ReadEnd;
static OVERLAPPED wol, rol;

// Winsock async operation handle
static HANDLE HAsync=0;

BOOL TCPIPClosed = TRUE;

/* Printer port handle for
   direct pass-thru printing */
static HANDLE PrnID = INVALID_HANDLE_VALUE;
static BOOL LPTFlag;

// Initialize ComVar.
// This routine is called only once
// by the initialization procedure of Tera Term.
void CommInit(PComVar cv)
{
	cv->Open = FALSE;
	cv->Ready = FALSE;

/* message flag */
	cv->NoMsg = 0;

	cv->isSSH = 0;
	cv->TitleRemoteW = NULL;

	cv->NotifyIcon = NULL;

	cv->ConnectedTime = 0;
}

/* reset a serial port which is already open */
void CommResetSerial(PTTSet ts, PComVar cv, BOOL ClearBuff)
{
	DCB dcb;
	DWORD DErr;
	COMMTIMEOUTS ctmo;

	if (! cv->Open ||
		(cv->PortType != IdSerial)) {
			return;
	}

	ClearCommError(cv->ComID,&DErr,NULL);
	SetupComm(cv->ComID,CommInQueSize,CommOutQueSize);
	/* flush input and output buffers */
	if (ClearBuff) {
		PurgeComm(cv->ComID, PURGE_TXABORT | PURGE_RXABORT |
		                     PURGE_TXCLEAR | PURGE_RXCLEAR);
	}

	memset(&ctmo,0,sizeof(ctmo));
	ctmo.ReadIntervalTimeout = MAXDWORD;
	ctmo.WriteTotalTimeoutConstant = 500;
	SetCommTimeouts(cv->ComID,&ctmo);
	cv->InBuffCount = 0;
	cv->InPtr = 0;
	cv->OutBuffCount = 0;
	cv->OutPtr = 0;

	cv->DelayPerChar = ts->DelayPerChar;
	cv->DelayPerLine = ts->DelayPerLine;

	memset(&dcb,0,sizeof(DCB));
	dcb.DCBlength = sizeof(DCB);
	dcb.BaudRate = ts->Baud;
	dcb.fBinary = TRUE;
	switch (ts->Parity) {
		case IdParityNone:
			dcb.Parity = NOPARITY;
			break;
		case IdParityOdd:
			dcb.fParity = TRUE;
			dcb.Parity = ODDPARITY;
			break;
		case IdParityEven:
			dcb.fParity = TRUE;
			dcb.Parity = EVENPARITY;
			break;
		case IdParityMark:
			dcb.fParity = TRUE;
			dcb.Parity = MARKPARITY;
			break;
		case IdParitySpace:
			dcb.fParity = TRUE;
			dcb.Parity = SPACEPARITY;
			break;
	}

	dcb.fDtrControl = DTR_CONTROL_ENABLE;
	dcb.fRtsControl = RTS_CONTROL_ENABLE;
	switch (ts->Flow) {
		case IdFlowX:
			dcb.fOutX = TRUE;
			dcb.fInX = TRUE;
			dcb.XonLim = CommXonLim;
			dcb.XoffLim = CommXoffLim;
			dcb.XonChar = XON;
			dcb.XoffChar = XOFF;
			break;
		case IdFlowHard:  // RTS/CTS
			dcb.fOutxCtsFlow = TRUE;
			dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
			break;
		case IdFlowHardDsrDtr:  // DSR/DTR
			dcb.fOutxDsrFlow = TRUE;
			dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;
			break;
	}

	switch (ts->DataBit) {
		case IdDataBit7:
			dcb.ByteSize = 7;
			break;
		case IdDataBit8:
			dcb.ByteSize = 8;
			break;
	}
	switch (ts->StopBit) {
		case IdStopBit1:
			dcb.StopBits = ONESTOPBIT;
			break;
		case IdStopBit2:
			dcb.StopBits = TWOSTOPBITS;
			break;
	}

	SetCommState(cv->ComID, &dcb);

	/* enable receive request */
	SetCommMask(cv->ComID,0);
	SetCommMask(cv->ComID,EV_RXCHAR);
}

// 名前付きパイプが正しい書式かをチェックする。
// \\ServerName\pipe\PipeName
//
// return  0: 正しい
//        -1: 不正
// (2012.3.10 yutaka)
int CheckNamedPipeFormat(char *p, int size)
{
	int ret = -1;
	char *s;

	if (size <= 8)
		goto error;

	if (p[0] == '\\' && p[1] == '\\') {
		s = strchr(&p[2], '\\');
		if (s && _strnicmp(s+1, "pipe\\", 5) == 0) {
			ret = 0;
		}
	}

error:
	return (ret);
}

void CommOpen(HWND HW, PTTSet ts, PComVar cv)
{
	char ErrMsg[21 + 256];
	wchar_t ErrMsgW[21 + 256];
	char P[50+256];

	MSG Msg;
	ADDRINFO hints;
	char pname[NI_MAXSERV];

	BOOL InvalidHost;

	// ホスト名が名前付きパイプかどうかを調べる。
	if (ts->PortType == IdTCPIP) {
		if (CheckNamedPipeFormat(ts->HostName, strlen(ts->HostName)) == 0) {
			ts->PortType = IdNamedPipe;
		}
	}

	/* initialize ComVar */
	cv->InBuffCount = 0;
	cv->InPtr = 0;
	cv->OutBuffCount = 0;
	cv->OutPtr = 0;
	cv->HWin = HW;
	cv->Ready = FALSE;
	cv->Open = FALSE;
	cv->PortType = ts->PortType;
	cv->ComPort = 0;
	cv->RetryCount = 0;
	cv->RetryWithOtherProtocol = TRUE;
	cv->s = INVALID_SOCKET;
	cv->ComID = INVALID_HANDLE_VALUE;
	cv->CanSend = TRUE;
	cv->RRQ = FALSE;
	cv->SendCode = IdASCII;
	cv->EchoCode = IdASCII;
	cv->Language = ts->Language;
	cv->CRSend = ts->CRSend;
	cv->KanjiCodeEcho = ts->KanjiCode;
	cv->JIS7KatakanaEcho = ts->JIS7Katakana;
	cv->KanjiCodeSend = ts->KanjiCodeSend;
	cv->JIS7KatakanaSend = ts->JIS7KatakanaSend;
	cv->KanjiIn = ts->KanjiIn;
	cv->KanjiOut = ts->KanjiOut;
	cv->DelayFlag = TRUE;
	cv->DelayPerChar = ts->DelayPerChar;
	cv->DelayPerLine = ts->DelayPerLine;
	cv->TelBinRecv = FALSE;
	cv->TelBinSend = FALSE;
	cv->TelFlag = FALSE;
	cv->TelMode = FALSE;
	cv->IACFlag = FALSE;
	cv->TelCRFlag = FALSE;
	cv->TelCRSend = FALSE;
	cv->TelCRSendEcho = FALSE;
	cv->TelAutoDetect = ts->TelAutoDetect; /* TTPLUG */
	cv->ConnetingTimeout = &ts->ConnectingTimeout;
	cv->LastSendTime = time(NULL);
	cv->LineModeBuffCount = 0;
	cv->Flush = FALSE;
	cv->FlushLen = 0;
	cv->TelLineMode = FALSE;
	cv->ConnectedTime = 0;

	if ((ts->PortType!=IdSerial) && (strlen(ts->HostName)==0))
	{
		PostMessage(cv->HWin, WM_USER_COMMNOTIFY, 0, FD_CLOSE);
		return;
	}

	switch (ts->PortType) {
		case IdTCPIP:
			cv->TelFlag = (ts->Telnet > 0);
			if (ts->EnableLineMode) {
				cv->TelLineMode = TRUE;
			}
			if (! LoadWinsock()) {
				if (cv->NoMsg==0) {
					static const TTMessageBoxInfoW info = {
						"Tera Term",
						"MSG_TT_ERROR", L"Tera Term: Error",
						"MSG_WINSOCK_ERROR", L"Cannot use winsock",
						MB_TASKMODAL | MB_ICONEXCLAMATION,
					};
					TTMessageBoxW(cv->HWin, &info, ts->UILanguageFileW);
				}
				InvalidHost = TRUE;
			}
			else {
				TTXOpenTCP(); /* TTPLUG */
				cv->Open = TRUE;
				/* resolving address */
				memset(&hints, 0, sizeof(hints));
				hints.ai_family = ts->ProtocolFamily;
				hints.ai_socktype = SOCK_STREAM;
				hints.ai_protocol = IPPROTO_TCP;
				_snprintf_s(pname, sizeof(pname), _TRUNCATE, "%d", ts->TCPPort);

				HAsync = PWSAAsyncGetAddrInfo(HW, WM_USER_GETHOST,
				                             ts->HostName, pname, &hints, &cv->res0);
				if (HAsync == 0)
					InvalidHost = TRUE;
				else {
					cv->ComPort = 1; // set "getting host" flag
					                 //  (see CVTWindow::OnSysCommand())
					do {
						if (GetMessage(&Msg,0,0,0)) {
							if ((Msg.hwnd==HW) &&
							    ((Msg.message == WM_SYSCOMMAND) &&
							     ((Msg.wParam & 0xfff0) == SC_CLOSE) ||
							     (Msg.message == WM_COMMAND) &&
							     (LOWORD(Msg.wParam) == ID_FILE_EXIT) ||
							     (Msg.message == WM_CLOSE))) { /* Exit when the user closes Tera Term */
								PWSACancelAsyncRequest(HAsync);
								CloseHandle(HAsync);
								HAsync = 0;
								cv->ComPort = 0; // clear "getting host" flag
								PostMessage(HW,Msg.message,Msg.wParam,Msg.lParam);
								return;
							}
							if (Msg.message != WM_USER_GETHOST) { /* Prosess messages */
								TranslateMessage(&Msg);
								DispatchMessage(&Msg);
							}
						}
						else {
							return;
						}
					} while (Msg.message!=WM_USER_GETHOST);
					cv->ComPort = 0; // clear "getting host" flag
					CloseHandle(HAsync);
					HAsync = 0;
					InvalidHost = WSAGETASYNCERROR(Msg.lParam) != 0;
				}
			} /* if (!LoadWinsock()) */

			if (InvalidHost) {
				if (cv->NoMsg==0) {
					static const TTMessageBoxInfoW info = {
						"Tera Term",
						"MSG_TT_ERROR", L"Tera Term: Error",
						"MSG_INVALID_HOST_ERROR", L"Invalid host",
						MB_TASKMODAL | MB_ICONEXCLAMATION | MB_HELP
					};
					VtwinSetHelpId(HlpAboutQandaConnection);
					TTMessageBoxW(cv->HWin, &info, ts->UILanguageFileW);
				}
				goto BreakSC;
			}
			for (cv->res = cv->res0; cv->res; cv->res = cv->res->ai_next) {
				cv->s =  OpenSocket(cv);
				if (cv->s == INVALID_SOCKET) {
					CloseSocket(cv->s);
					continue;
				}
				/* start asynchronous connect */
				AsyncConnect(cv);
				break; /* break for-loop immediately */
			}
			break;

		case IdSerial:
			InitFileIO(IdSerial);  /* TTPLUG */
			TTXOpenFile(); /* TTPLUG */
			_snprintf_s(P, sizeof(P), _TRUNCATE, "COM%d", ts->ComPort);
			strncpy_s(ErrMsg, sizeof(ErrMsg),P, _TRUNCATE);
			strncpy_s(P, sizeof(P),"\\\\.\\", _TRUNCATE);
			strncat_s(P, sizeof(P),ErrMsg, _TRUNCATE);
			cv->ComID = PCreateFile(P, GENERIC_READ | GENERIC_WRITE, 0, NULL,
			                        OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
			if (cv->ComID == INVALID_HANDLE_VALUE ) {
				if (cv->NoMsg==0) {
					DWORD err = GetLastError();
					wchar_t *format;
					wchar_t *PW = ToWcharA(&P[4]);
					static const TTMessageBoxInfoW info = {
						"Tera Term",
						"MSG_TT_ERROR", L"Tera Term: Error",
						NULL, NULL,
						MB_TASKMODAL | MB_ICONEXCLAMATION
					};

					switch (err) {
					case ERROR_FILE_NOT_FOUND:
						format = TTGetLangStrW("Tera Term", "MSG_CANTOPEN_ERROR_NOTFOUND", L"Cannot open %s. Not found.", ts->UILanguageFileW);
						_snwprintf_s(ErrMsgW, _countof(ErrMsgW), _TRUNCATE, format, PW);
						break;
					case ERROR_ACCESS_DENIED:
						format = TTGetLangStrW("Tera Term", "MSG_CANTOPEN_ERROR_DENIED", L"Cannot open %s. Access denied.", ts->UILanguageFileW);
						_snwprintf_s(ErrMsgW, _countof(ErrMsgW), _TRUNCATE, format, PW);
						break;
					default:
						format = TTGetLangStrW("Tera Term", "MSG_CANTOPEN_ERROR", L"Cannot open %s. (0x%08x)", ts->UILanguageFileW);
						_snwprintf_s(ErrMsgW, _countof(ErrMsgW), _TRUNCATE, format, PW, err);
						break;
					}
					free(format);
					free(PW);

					TTMessageBoxW(cv->HWin, &info, ts->UILanguageFileW, ErrMsgW);
				}
				InvalidHost = TRUE;
			}
			else {
				cv->Open = TRUE;
				cv->ComPort = ts->ComPort;
				CommResetSerial(ts, cv, ts->ClearComBuffOnOpen);
				if (!ts->ClearComBuffOnOpen) {
					cv->RRQ = TRUE;
				}

				/* notify to VT window that Comm Port is open */
				PostMessage(cv->HWin, WM_USER_COMMOPEN, 0, 0);
				InvalidHost = FALSE;

				SetCOMFlag(ts->ComPort);
			}
			break; /* end of "case IdSerial:" */

		case IdFile:
			InitFileIO(IdFile);  /* TTPLUG */
			TTXOpenFile(); /* TTPLUG */
			cv->ComID = PCreateFile(ts->HostName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
			InvalidHost = (cv->ComID == INVALID_HANDLE_VALUE);
			if (InvalidHost) {
				if (cv->NoMsg==0) {
					static const TTMessageBoxInfoW info = {
						"Tera Term",
						"MSG_TT_ERROR", L"Tera Term: Error",
						"MSG_CANTOPEN_FILE_ERROR", L"Cannot open file",
						MB_TASKMODAL | MB_ICONEXCLAMATION
					};
					TTMessageBoxW(cv->HWin, &info, ts->UILanguageFileW);
				}
			}
			else {
				cv->Open = TRUE;
				PostMessage(cv->HWin, WM_USER_COMMOPEN, 0, 0);
			}
			break;

		case IdNamedPipe:
			InitFileIO(IdNamedPipe);  /* TTPLUG */
			TTXOpenFile(); /* TTPLUG */

			memset(P, 0, sizeof(P));
			strncpy_s(P, sizeof(P), ts->HostName, _TRUNCATE);

			// 名前付きパイプが正しい書式かをチェックする。
			if (CheckNamedPipeFormat(P, strlen(P)) < 0) {
				static const TTMessageBoxInfoW info = {
					"Tera Term",
					"MSG_TT_ERROR", L"Tera Term: Error",
					NULL, NULL,
					MB_TASKMODAL | MB_ICONEXCLAMATION
				};
				InvalidHost = TRUE;

				_snwprintf_s(ErrMsgW, _countof(ErrMsgW), _TRUNCATE,
					L"Invalid pipe name (%d)\n\n"
					L"A valid pipe name has the form\n"
					L"\"\\\\<ServerName>\\pipe\\<PipeName>\"",
					GetLastError());
				TTMessageBoxW(cv->HWin, &info, ts->UILanguageFileW, ErrMsgW);
				break;
			}

			cv->ComID = PCreateFile(P, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
			                        0,  // ブロッキングモードにする(FILE_FLAG_OVERLAPPED は指定しない)
			                        NULL);
			if (cv->ComID == INVALID_HANDLE_VALUE ) {
				if (cv->NoMsg==0) {
					DWORD err = GetLastError();
					wchar_t *format;
					wchar_t* PW = ToWcharA(&P[4]);
					static const TTMessageBoxInfoW info = {
						"Tera Term",
						"MSG_TT_ERROR", L"Tera Term: Error",
						NULL, NULL,
						MB_TASKMODAL | MB_ICONEXCLAMATION
					};

					switch (err) {
					case ERROR_FILE_NOT_FOUND:
						format = TTGetLangStrW("Tera Term", "MSG_CANTOPEN_ERROR_NOTFOUND", L"Cannot open %s. Not found.", ts->UILanguageFileW);
						_snwprintf_s(ErrMsgW, _countof(ErrMsgW), _TRUNCATE, format, PW);
						break;
					case ERROR_ACCESS_DENIED:
						format = TTGetLangStrW("Tera Term", "MSG_CANTOPEN_ERROR_DENIED", L"Cannot open %s. Access denied.", ts->UILanguageFileW);
						_snwprintf_s(ErrMsgW, _countof(ErrMsgW), _TRUNCATE, format, PW);
						break;
					case ERROR_PIPE_BUSY:
						format = TTGetLangStrW("Tera Term", "MSG_CANTOPEN_ERROR_PIPEBUSY", L"Cannot open %s. Pipe is busy.", ts->UILanguageFileW);
						_snwprintf_s(ErrMsgW, _countof(ErrMsgW), _TRUNCATE, format, PW);
						break;
					default:
						format = TTGetLangStrW("Tera Term", "MSG_CANTOPEN_ERROR", L"Cannot open %s. (0x%08x)", ts->UILanguageFileW);
						_snwprintf_s(ErrMsgW, _countof(ErrMsgW), _TRUNCATE, format, PW, err);
						break;
					}
					free(format);
					free(PW);

					TTMessageBoxW(cv->HWin, &info, ts->UILanguageFileW, ErrMsgW);
				}
				InvalidHost = TRUE;
			}
			else {
				cv->Open = TRUE;
				PostMessage(cv->HWin, WM_USER_COMMOPEN, 0, 0);
				InvalidHost = FALSE;
			}
			break; /* end of "case IdNamedPipe:" */

	} /* end of "switch" */

BreakSC:
	if (InvalidHost) {
		PostMessage(cv->HWin, WM_USER_COMMNOTIFY, 0, FD_CLOSE);
		if ( (ts->PortType==IdTCPIP) && cv->Open ) {
			if ( cv->s!=INVALID_SOCKET ) {
				Pclosesocket(cv->s);
				cv->s = INVALID_SOCKET;  /* ソケット無効の印を付ける。(2010.8.6 yutaka) */
			}
			FreeWinsock();
		}
		return;
	}
}

// 名前付きパイプ用スレッド
void NamedPipeThread(void *arg)
{
	PComVar cv = (PComVar)arg;
	DWORD DErr;
	HANDLE REnd;
	char Temp[20];
	char Buffer[1];  // 1byte
	DWORD BytesRead, TotalBytesAvail, BytesLeftThisMessage;

	_snprintf_s(Temp, sizeof(Temp), _TRUNCATE, "%s%d", READENDNAME, cv->ComPort);
	REnd = OpenEvent(EVENT_ALL_ACCESS,FALSE, Temp);
	while (TRUE) {
		BytesRead = 0;
		// 名前付きパイプはイベントを待つことができない仕様なので、キューの中身を
		// 覗き見することで、ReadFile() するかどうか判断する。
		if (PeekNamedPipe(cv->ComID, Buffer, sizeof(Buffer), &BytesRead, &TotalBytesAvail, &BytesLeftThisMessage)) {
			if (! cv->Ready) {
				_endthread();
			}
			if (BytesRead == 0) {  // 空だったら、何もしない。
				Sleep(1);
				continue;
			}
			if (! cv->RRQ) {
				PostMessage(cv->HWin, WM_USER_COMMNOTIFY, 0, FD_READ);
			}
			// ReadFile() が終わるまで待つ。
			WaitForSingleObject(REnd,INFINITE);
		}
		else {
			DErr = GetLastError();
			// [VMware] this returns 109 (broken pipe) if a named pipe is removed.
			// [Virtual Box] this returns 233 (pipe not connected) if a named pipe is removed.
			if (! cv->Ready || ERROR_BROKEN_PIPE == DErr || ERROR_PIPE_NOT_CONNECTED == DErr) {
				PostMessage(cv->HWin, WM_USER_COMMNOTIFY, 0, FD_CLOSE);
				_endthread();
			}
		}
	}
}

void CommThread(void *arg)
{
	DWORD Evt;
	PComVar cv = (PComVar)arg;
	DWORD DErr;
	HANDLE REnd;
	char Temp[20];

	_snprintf_s(Temp, sizeof(Temp), _TRUNCATE, "%s%d", READENDNAME, cv->ComPort);
	REnd = OpenEvent(EVENT_ALL_ACCESS,FALSE, Temp);
	while (TRUE) {
		if (WaitCommEvent(cv->ComID,&Evt,NULL)) {
			if (! cv->Ready) {
				_endthread();
			}
			if (! cv->RRQ) {
				PostMessage(cv->HWin, WM_USER_COMMNOTIFY, 0, FD_READ);
			}
			WaitForSingleObject(REnd,INFINITE);
		}
		else {
			DErr = GetLastError();  // this returns 995 (operation aborted) if a USB com port is removed
			if (! cv->Ready || ERROR_OPERATION_ABORTED == DErr) {
				_endthread();
			}
			ClearCommError(cv->ComID,&DErr,NULL);
		}
	}
}

void CommStart(PComVar cv, LONG lParam, PTTSet ts)
{
	wchar_t ErrMsgW[31];
	char Temp[20];
	wchar_t *UIMsgW;

	if (! cv->Open ) {
		return;
	}
	if ( cv->Ready ) {
		return;
	}

	// キャンセルタイマがあれば取り消す。ただし、この時点で WM_TIMER が送られている可能性はある。
	if (*cv->ConnetingTimeout > 0) {
		KillTimer(cv->HWin, IdCancelConnectTimer);
	}

	switch (cv->PortType) {
		case IdTCPIP:
			switch (HIWORD(lParam)) {
				case WSAECONNREFUSED:
					GetI18nStrWW("Tera Term", "MSG_COMM_REFUSE_ERROR", L"Connection refused", ts->UILanguageFileW, &UIMsgW);
					_snwprintf_s(ErrMsgW, _countof(ErrMsgW), _TRUNCATE, L"%s", UIMsgW);
					free(UIMsgW);
					break;
				case WSAENETUNREACH:
					GetI18nStrWW("Tera Term", "MSG_COMM_REACH_ERROR", L"Network cannot be reached", ts->UILanguageFileW, &UIMsgW);
					_snwprintf_s(ErrMsgW, _countof(ErrMsgW), _TRUNCATE, L"%s", UIMsgW);
					free(UIMsgW);
					break;
				case WSAETIMEDOUT:
					GetI18nStrWW("Tera Term", "MSG_COMM_CONNECT_ERROR", L"Connection timed out", ts->UILanguageFileW, &UIMsgW);
					_snwprintf_s(ErrMsgW, _countof(ErrMsgW), _TRUNCATE, L"%s", UIMsgW);
					free(UIMsgW);
					break;
				default:
					GetI18nStrWW("Tera Term", "MSG_COMM_TIMEOUT_ERROR", L"Cannot connect the host", ts->UILanguageFileW, &UIMsgW);
					_snwprintf_s(ErrMsgW, _countof(ErrMsgW), _TRUNCATE, L"%s", UIMsgW);
					free(UIMsgW);
					break;
			}
			if (HIWORD(lParam)>0) {
				/* connect() failed */
				if (cv->res->ai_next != NULL) {
					/* try to connect with other protocol */
					CloseSocket(cv->s);
					for (cv->res = cv->res->ai_next; cv->res; cv->res = cv->res->ai_next) {
						cv->s = OpenSocket(cv);
						if (cv->s == INVALID_SOCKET) {
							CloseSocket(cv->s);
							continue;
						}
						AsyncConnect(cv);
						cv->Ready = FALSE;
						cv->RetryWithOtherProtocol = TRUE; /* retry with other procotol */
						return;
					}
				} else {
					/* trying with all protocol family are failed */
					if (cv->NoMsg==0)
					{
						static const TTMessageBoxInfoW info = {
							"Tera Term",
							"MSG_TT_ERROR", L"Tera Term: Error",
							NULL, NULL,
							MB_TASKMODAL | MB_ICONEXCLAMATION
						};
						TTMessageBoxW(cv->HWin, &info, ts->UILanguageFileW, ErrMsgW);
					}
					PostMessage(cv->HWin, WM_USER_COMMNOTIFY, 0, FD_CLOSE);
					cv->RetryWithOtherProtocol = FALSE;
					return;
				}
			}

			/* here is connection established */
			cv->RetryWithOtherProtocol = FALSE;
			PWSAAsyncSelect(cv->s,cv->HWin,WM_USER_COMMNOTIFY, FD_READ | FD_OOB | FD_CLOSE);
			TCPIPClosed = FALSE;
			break;

		case IdSerial:
			_snprintf_s(Temp, sizeof(Temp), _TRUNCATE, "%s%d", READENDNAME, cv->ComPort);
			ReadEnd = CreateEvent(NULL,FALSE,FALSE,Temp);
			_snprintf_s(Temp, sizeof(Temp), _TRUNCATE, "%s%d", WRITENAME, cv->ComPort);
			memset(&wol,0,sizeof(OVERLAPPED));
			wol.hEvent = CreateEvent(NULL,TRUE,TRUE,Temp);
			_snprintf_s(Temp, sizeof(Temp), _TRUNCATE, "%s%d", READNAME, cv->ComPort);
			memset(&rol,0,sizeof(OVERLAPPED));
			rol.hEvent = CreateEvent(NULL,TRUE,FALSE,Temp);

			/* create the receiver thread */
			if (_beginthread(CommThread,0,cv) == -1) {
				static const TTMessageBoxInfoW info = {
					"Tera Term",
					"MSG_TT_ERROR", L"Tera Term: Error",
					"MSG_TT_ERROR", L"Can't create thread",
					MB_TASKMODAL | MB_ICONEXCLAMATION
				};
				TTMessageBoxW(cv->HWin, &info, ts->UILanguageFileW);
			}
			break;

		case IdFile:
			cv->RRQ = TRUE;
			break;

		case IdNamedPipe:
			cv->ComPort = 0;
			_snprintf_s(Temp, sizeof(Temp), _TRUNCATE, "%s%d", READENDNAME, cv->ComPort);
			ReadEnd = CreateEvent(NULL,FALSE,FALSE,Temp);
			_snprintf_s(Temp, sizeof(Temp), _TRUNCATE, "%s%d", WRITENAME, cv->ComPort);
			memset(&wol,0,sizeof(OVERLAPPED));
			wol.hEvent = CreateEvent(NULL,TRUE,TRUE,Temp);
			_snprintf_s(Temp, sizeof(Temp), _TRUNCATE, "%s%d", READNAME, cv->ComPort);
			memset(&rol,0,sizeof(OVERLAPPED));
			rol.hEvent = CreateEvent(NULL,TRUE,FALSE,Temp);

			/* create the receiver thread */
			if (_beginthread(NamedPipeThread,0,cv) == -1) {
				static const TTMessageBoxInfoW info = {
					"Tera Term",
					"MSG_TT_ERROR", L"Tera Term: Error",
					"MSG_TT_ERROR", L"Can't create thread",
					MB_TASKMODAL | MB_ICONEXCLAMATION
				};
				TTMessageBoxW(cv->HWin, &info, ts->UILanguageFileW);
			}
			break;
	}
	cv->Ready = TRUE;
	cv->ConnectedTime = GetTickCount();
}

BOOL CommCanClose(PComVar cv)
{ // check if data remains in buffer
	if (! cv->Open) {
		return TRUE;
	}
	if (cv->InBuffCount>0) {
		return FALSE;
	}
	if (FLogIsOpend() && FLogGetCount() > 0) {
		return FALSE;
	}
	if (DDELog && DDEGetCount() > 0) {
		return FALSE;
	}
	return TRUE;
}

void CommClose(PComVar cv)
{
	if ( ! cv->Open ) {
		return;
	}
	cv->Open = FALSE;

	/* disable event message posting & flush buffer */
	cv->RRQ = FALSE;
	cv->Ready = FALSE;
	cv->InPtr = 0;
	cv->InBuffCount = 0;
	cv->OutPtr = 0;
	cv->OutBuffCount = 0;
	cv->LineModeBuffCount = 0;
	cv->FlushLen = 0;
	cv->Flush = FALSE;

	/* close port & release resources */
	switch (cv->PortType) {
		case IdTCPIP:
			if (HAsync!=0) {
				PWSACancelAsyncRequest(HAsync);
			}
			HAsync = 0;
			Pfreeaddrinfo(cv->res0);
			if ( cv->s!=INVALID_SOCKET ) {
				Pclosesocket(cv->s);
			}
			cv->s = INVALID_SOCKET;
			TTXCloseTCP(); /* TTPLUG */
			FreeWinsock();
			break;
		case IdSerial:
			if ( cv->ComID != INVALID_HANDLE_VALUE ) {
				CloseHandle(ReadEnd);
				CloseHandle(wol.hEvent);
				CloseHandle(rol.hEvent);
				PurgeComm(cv->ComID, PURGE_TXABORT | PURGE_RXABORT |
				                     PURGE_TXCLEAR | PURGE_RXCLEAR);
				EscapeCommFunction(cv->ComID,CLRDTR);
				SetCommMask(cv->ComID,0);
				PCloseFile(cv->ComID);
				ClearCOMFlag(cv->ComPort);
			}
			TTXCloseFile(); /* TTPLUG */
			break;
		case IdFile:
			if (cv->ComID != INVALID_HANDLE_VALUE) {
				PCloseFile(cv->ComID);
			}
			TTXCloseFile(); /* TTPLUG */
			break;

		case IdNamedPipe:
			if ( cv->ComID != INVALID_HANDLE_VALUE ) {
				CloseHandle(ReadEnd);
				CloseHandle(wol.hEvent);
				CloseHandle(rol.hEvent);
				PCloseFile(cv->ComID);
			}
			TTXCloseFile(); /* TTPLUG */
			break;
	}
	cv->ComID = INVALID_HANDLE_VALUE;
	cv->PortType = 0;
}

void CommProcRRQ(PComVar cv)
{
	if ( ! cv->Ready ) {
		return;
	}
	/* disable receive request */
	switch (cv->PortType) {
		case IdTCPIP:
			if (! TCPIPClosed) {
				PWSAAsyncSelect(cv->s,cv->HWin,WM_USER_COMMNOTIFY, FD_OOB | FD_CLOSE);
			}
			break;
		case IdSerial:
			break;
	}
	cv->RRQ = TRUE;
	CommReceive(cv);
}

void CommReceive(PComVar cv)
{
	DWORD C;
	DWORD DErr;

	if (! cv->Ready || ! cv->RRQ ||
	    (cv->InBuffCount>=InBuffSize)) {
		return;
	}

	/* Compact buffer */
	if ((cv->InBuffCount>0) && (cv->InPtr>0)) {
		memmove(cv->InBuff,&(cv->InBuff[cv->InPtr]),cv->InBuffCount);
		cv->InPtr = 0;
	}

	if (cv->InBuffCount<InBuffSize) {
		switch (cv->PortType) {
			case IdTCPIP:
				C = Precv(cv->s, &(cv->InBuff[cv->InBuffCount]),
				          InBuffSize-cv->InBuffCount, 0);
				if (C == SOCKET_ERROR) {
					C = 0;
					PWSAGetLastError();
				}
				cv->InBuffCount = cv->InBuffCount + C;
				break;
			case IdSerial:
				do {
					ClearCommError(cv->ComID,&DErr,NULL);
					if (! PReadFile(cv->ComID,&(cv->InBuff[cv->InBuffCount]),
					                InBuffSize-cv->InBuffCount,&C,&rol)) {
						if (GetLastError() == ERROR_IO_PENDING) {
							if (WaitForSingleObject(rol.hEvent, 1000) != WAIT_OBJECT_0) {
								C = 0;
							}
							else {
								GetOverlappedResult(cv->ComID,&rol,&C,FALSE);
							}
						}
						else {
							C = 0;
						}
					}
					cv->InBuffCount = cv->InBuffCount + C;
				} while ((C!=0) && (cv->InBuffCount<InBuffSize));
				ClearCommError(cv->ComID,&DErr,NULL);
				break;
			case IdFile:
				if (PReadFile(cv->ComID,&(cv->InBuff[cv->InBuffCount]),
				              InBuffSize-cv->InBuffCount,&C,NULL)) {
					if (C == 0) {
						DErr = ERROR_HANDLE_EOF;
					}
					else {
						cv->InBuffCount = cv->InBuffCount + C;
					}
				}
				else {
					DErr = GetLastError();
				}
				break;

			case IdNamedPipe:
				// キューの中に最低1バイト以上のデータが入っていることを確認できているため、
				// ReadFile() はブロックすることはないため、一括して読む。
				if (PReadFile(cv->ComID,&(cv->InBuff[cv->InBuffCount]),
				              InBuffSize-cv->InBuffCount,&C,NULL)) {
					if (C == 0) {
						DErr = ERROR_HANDLE_EOF;
					}
					else {
						cv->InBuffCount = cv->InBuffCount + C;
					}
				}
				else {
					DErr = GetLastError();
				}

				// 1バイト以上読めたら、イベントを起こし、スレッドを再開させる。
				if (cv->InBuffCount > 0) {
					cv->RRQ = FALSE;
					SetEvent(ReadEnd);
				}
				break;
		}
	}

	if (cv->InBuffCount==0) {
		switch (cv->PortType) {
			case IdTCPIP:
				if (! TCPIPClosed) {
					PWSAAsyncSelect(cv->s,cv->HWin, WM_USER_COMMNOTIFY,
					                FD_READ | FD_OOB | FD_CLOSE);
				}
				break;
			case IdSerial:
				cv->RRQ = FALSE;
				SetEvent(ReadEnd);
				return;
			case IdFile:
				if (DErr != ERROR_IO_PENDING) {
					PostMessage(cv->HWin, WM_USER_COMMNOTIFY, 0, FD_CLOSE);
					cv->RRQ = FALSE;
				}
				else {
					cv->RRQ = TRUE;
				}
				return;
			case IdNamedPipe:
				// TODO: たぶん、ここに来ることはない。
				if (DErr != ERROR_IO_PENDING) {
					PostMessage(cv->HWin, WM_USER_COMMNOTIFY, 0, FD_CLOSE);
					cv->RRQ = FALSE;
				}
				else {
					cv->RRQ = TRUE;
				}
				SetEvent(ReadEnd);
				return;
		}
		cv->RRQ = FALSE;
	}
}

void CommSend(PComVar cv)
{
	int delay;
	COMSTAT Stat;
	BYTE LineEnd;
	int C, D, Max;
	DWORD DErr;

	if ((! cv->Open) || (! cv->Ready)) {
		cv->OutBuffCount = 0;
		return;
	}

	if ((cv->OutBuffCount == 0) || (! cv->CanSend)) {
		return;
	}

	/* Max num of bytes to be written */
	switch (cv->PortType) {
		case IdTCPIP:
			if (TCPIPClosed) {
				cv->OutBuffCount = 0;
			}
			Max = cv->OutBuffCount;
			break;
		case IdSerial:
			ClearCommError(cv->ComID,&DErr,&Stat);
			Max = OutBuffSize - Stat.cbOutQue;
			break;
		case IdFile:
			Max = cv->OutBuffCount;
			break;
		case IdNamedPipe:
			Max = cv->OutBuffCount;
			break;
	}

	if ( Max<=0 ) {
		return;
	}
	if ( Max > cv->OutBuffCount ) {
		Max = cv->OutBuffCount;
	}

	if (cv->PortType == IdTCPIP && cv->TelFlag) {
		cv->LastSendTime = time(NULL);
	}

	C = Max;
	delay = 0;

	if ( cv->DelayFlag && (cv->PortType==IdSerial) ) {
		if ( cv->DelayPerLine > 0 ) {
			if ( cv->CRSend==IdCR ) {
				LineEnd = 0x0d;
			}
			else { // CRLF or LF
				LineEnd = 0x0a;
			}
			C = 1;
			if ( cv->DelayPerChar==0 ) {
				while ((C<Max) && (cv->OutBuff[cv->OutPtr+C-1]!=LineEnd)) {
					C++;
				}
			}
			if ( cv->OutBuff[cv->OutPtr+C-1]==LineEnd ) {
				delay = cv->DelayPerLine;
			}
			else {
				delay = cv->DelayPerChar;
			}
		}
		else if ( cv->DelayPerChar > 0 ) {
			C = 1;
			delay = cv->DelayPerChar;
		}
	}

	/* Write to comm driver/Winsock */
	switch (cv->PortType) {
		case IdTCPIP:
			D = Psend(cv->s, &(cv->OutBuff[cv->OutPtr]), C, 0);
			if ( D==SOCKET_ERROR ) { /* if error occurs */
				PWSAGetLastError(); /* Clear error */
				D = 0;
			}
			break;

		case IdSerial:
			if (! PWriteFile(cv->ComID,&(cv->OutBuff[cv->OutPtr]),C,(LPDWORD)&D,&wol)) {
				if (GetLastError() == ERROR_IO_PENDING) {
					if (WaitForSingleObject(wol.hEvent,1000) != WAIT_OBJECT_0) {
						D = C; /* Time out, ignore data */
					}
					else {
						GetOverlappedResult(cv->ComID,&wol,(LPDWORD)&D,FALSE);
					}
				}
				else { /* I/O error */
					D = C; /* ignore error */
				}
			}
			ClearCommError(cv->ComID,&DErr,&Stat);
			break;

		case IdFile:
			if (! PWriteFile(cv->ComID, &(cv->OutBuff[cv->OutPtr]), C, (LPDWORD)&D, NULL)) {
				if (! (GetLastError() == ERROR_IO_PENDING)) {
					D = C; /* ignore data */
				}
			}
			break;

		case IdNamedPipe:
			if (! PWriteFile(cv->ComID, &(cv->OutBuff[cv->OutPtr]), C, (LPDWORD)&D, NULL)) {
				// ERROR_IO_PENDING 以外のエラーだったら、パイプがクローズされているかもしれないが、
				// 送信できたことにする。
				if (! (GetLastError() == ERROR_IO_PENDING)) {
					D = C; /* ignore data */
				}
			}
			break;
	}

	cv->OutBuffCount = cv->OutBuffCount - D;
	if ( cv->OutBuffCount==0 ) {
		cv->OutPtr = 0;
	}
	else {
		cv->OutPtr = cv->OutPtr + D;
	}

	if ( (C==D) && (delay>0) ) {
		cv->CanSend = FALSE;
		SetTimer(cv->HWin, IdDelayTimer, delay, NULL);
	}
}

void CommSendBreak(PComVar cv, int msec)
/* for only serial ports */
{
	MSG DummyMsg;

	if ( ! cv->Ready ) {
		return;
	}

	switch (cv->PortType) {
		case IdSerial:
			/* Set com port into a break state */
			SetCommBreak(cv->ComID);

			/* pause for 1 sec */
			if (SetTimer(cv->HWin, IdBreakTimer, msec, NULL) != 0) {
				GetMessage(&DummyMsg,cv->HWin,WM_TIMER,WM_TIMER);
			}

			/* Set com port into the nonbreak state */
			ClearCommBreak(cv->ComID);
			break;
	}
}

void CommLock(PTTSet ts, PComVar cv, BOOL Lock)
{
	BYTE b;
	DWORD Func;

	if (! cv->Ready) {
		return;
	}
	if ((cv->PortType==IdTCPIP) ||
	    (cv->PortType==IdSerial) &&
			(!(ts->Flow == IdFlowHard || ts->Flow == IdFlowHardDsrDtr))
			) {
		if (Lock) {
			b = XOFF;
		}
		else {
			b = XON;
		}
		CommBinaryOut(cv,&b,1);
	}
	else if ((cv->PortType==IdSerial) &&
	         (ts->Flow == IdFlowHard || ts->Flow == IdFlowHardDsrDtr)) {
		// ハードウェアフローの設定に応じて拡張機能コードを切り替える。
		if (Lock) {
			Func = CLRRTS;
			if (ts->Flow == IdFlowHardDsrDtr)
				Func = CLRDTR;
		}
		else {
			Func = SETRTS;
			if (ts->Flow == IdFlowHardDsrDtr)
				Func = SETDTR;
		}
		EscapeCommFunction(cv->ComID,Func);
	}
}

BOOL PrnOpen(PCHAR DevName)
{
	char Temp[MAXPATHLEN], *c;
	DCB dcb;
	DWORD DErr;
	COMMTIMEOUTS ctmo;

	strncpy_s(Temp, sizeof(Temp),DevName, _TRUNCATE);
	c = Temp;
	while (*c != '\0' && *c != ':') {
		c++;
	}
	*c = '\0';
	LPTFlag = (Temp[0]=='L') ||
	          (Temp[0]=='l');

	if (IsWindowsNTKernel()) {
		// ネットワーク共有にマップされたデバイスが相手の場合、こうしないといけないらしい (2011.01.25 maya)
		// http://logmett.com/forum/viewtopic.php?f=2&t=1383
		// http://msdn.microsoft.com/en-us/library/aa363858(v=vs.85).aspx#5
		PrnID = CreateFile(Temp,GENERIC_WRITE | FILE_READ_ATTRIBUTES,
		                   FILE_SHARE_READ,NULL,CREATE_ALWAYS,
		                   0,NULL);
	}
	else {
		// 9x では上記のコードでうまくいかないので従来通りの処理
		PrnID = CreateFile(Temp,GENERIC_WRITE,
		                   0,NULL,OPEN_EXISTING,
		                   0,NULL);
	}

	if (PrnID == INVALID_HANDLE_VALUE) {
		return FALSE;
	}

	if (GetCommState(PrnID,&dcb)) {
		BuildCommDCB(DevName,&dcb);
		SetCommState(PrnID,&dcb);
	}
	ClearCommError(PrnID,&DErr,NULL);
	if (! LPTFlag) {
		SetupComm(PrnID,0,CommOutQueSize);
	}
	/* flush output buffer */
	PurgeComm(PrnID, PURGE_TXABORT | PURGE_TXCLEAR);
	memset(&ctmo,0,sizeof(ctmo));
	ctmo.WriteTotalTimeoutConstant = 1000;
	SetCommTimeouts(PrnID,&ctmo);
	if (! LPTFlag) {
		EscapeCommFunction(PrnID,SETDTR);
	}
	return TRUE;
}

int PrnWrite(PCHAR b, int c)
{
	int d;
	DWORD DErr;
	COMSTAT Stat;

	if (PrnID == INVALID_HANDLE_VALUE ) {
		return c;
	}

	ClearCommError(PrnID,&DErr,&Stat);
	if (! LPTFlag &&
		(OutBuffSize - (int)Stat.cbOutQue < c)) {
		c = OutBuffSize - Stat.cbOutQue;
	}
	if (c<=0) {
		return 0;
	}
	if (! WriteFile(PrnID,b,c,(LPDWORD)&d,NULL)) {
		d = 0;
	}
	ClearCommError(PrnID,&DErr,NULL);
	return d;
}

void PrnCancel()
{
	PurgeComm(PrnID, PURGE_TXABORT | PURGE_TXCLEAR);
	PrnClose();
}

void PrnClose()
{
	if (PrnID != INVALID_HANDLE_VALUE) {
		if (!LPTFlag) {
			EscapeCommFunction(PrnID,CLRDTR);
		}
		CloseHandle(PrnID);
	}
	PrnID = INVALID_HANDLE_VALUE;
}
