{*******************************************************}
{                                                       }
{       Tocsg.ClientBase                                }
{                                                       }
{       Copyright (C) 2022 sunk                         }
{                                                       }
{*******************************************************}

unit Tocsg.ClientBase;

interface

uses
  Tocsg.Obj, System.SysUtils, System.Classes, Winapi.Windows, Winapi.Winsock2,
  System.Win.ScktComp, Tocsg.Packet, Tocsg.Thread, IdTCPClient,
  System.SyncObjs, Tocsg.Encrypt, System.Generics.Collections,
  Tocsg.Process.IPC, Tocsg.Exception;

const
  PACKET_VERSION = 1;

type
  PClientRcvPacket = ^TClientRcvPacket;
  TClientRcvPacket = record
    wRank: WORD;
    llId: LONGLONG;
    pBuf: Pointer;
    nProcPktLen,
    nRemainPktLen: Integer;
  end;

  ETgClient = ETgException;
  TTgClientBase = class(TTgThread)
  private
    CS_: TCriticalSection;
    sHost_: String;
    nPort_: Integer;
    dwPingTick_,
    dwDisconnTick_: DWORD;
    nPingTerm_,
    nReConnTerm_: Integer;

  // 겹친 패킷 발견 시 나머지 받아놓고 나중에 재활용하는 버퍼
    _RcylBuf: TBytes;
    _nRcylBufLen,
    _nRcylBufPos: Integer;

    _pRcvPkt: PClientRcvPacket;
    _pInitBuf: TBytes;
    _DcRcvBufs: TDictionary<LONGLONG,PClientRcvPacket>;

  // 패킷 암호화 정보
    PktEncKind_: TTgEncKind;
    PktEncPass_: String;
    Enc_: TTgEncrypt;

  // 패킷 전송 정지
    bSendPause_: Boolean;

    procedure OnRcvBufNotify(Sender: TObject; const Item: PClientRcvPacket; Action: TCollectionNotification);

    procedure _ConnectedEvent(Sender: TObject);
    procedure _DisconnectedEvent(Sender: TObject);

    function GetPktEncKind: TTgEncKind;
    procedure SetConnected(const bVal: Boolean);
    function DequeueSendPacket: ISendPacket;
  protected
    Client_: TIdTCPClient;
    dtConn_: TDateTime;
    bConnected_,
    bTryReconnect_: Boolean;
    nSvrPktVer_: Integer;
    QSendPacket_: TQueue<ISendPacket>;

  // 프로세스 통신
    W2W_: TTgWnd2Wnd;
    hIpcWnd_: HWND;
    NpIpc_: TTgNpBase; // NamedPipe 통신

    function GetConnected: Boolean; virtual;
    function _ProcessRcv: Boolean;
    function _ProcessSend(Send: ISendPacket): Boolean; virtual;
    function _ProcessOther: Boolean; virtual;
    procedure _ProcessSendFail(Send: ISendPacket); virtual;

    procedure Execute; override;
    procedure SetHost(const sHost: String);
    procedure SetPort(const nPort: Integer);
    function GetTryReconnect: Boolean;
    procedure SetTryReconnect(const bVal: Boolean);
    procedure SetPacketEncryptInfo(aPktEncKind: TTgEncKind; const sPktEncPass: String);

    procedure ConnectedEvent; virtual;
    procedure DisconnectedEvent; virtual;
    procedure ProcessRcvPacket(aRcv: IRcvPacket); virtual;

    function GetSendPauseState: Boolean;
    procedure SetSendPauseState(bVal: Boolean);
    procedure OnW2WConnection(Sender: TTgObject; aState: TW2wConnState; hRcvWnd: HWND);
    procedure OnNpConnected(Sender: TTgNpBase; hPipe: THandle);
    procedure OnNpDisconnected(Sender: TTgNpBase; hPipe: THandle);
  public
    Constructor Create(const sHost: String; nPort: Integer;
            nReConnTerm: Integer = 5000; nPingTerm: Integer = 60000);
    Destructor Destroy; override;

    procedure Lock;
    procedure Unlock;

    procedure SendPacket(Send: ISendPacket); virtual;
    procedure DirectSendPacket(Send: ISendPacket);

    function Connect(const sHost: String; nPort: Integer): Boolean; overload;
    function Connect: Boolean; overload;

    function GetSelfWnd: HWND;
    function ActiveW2W(sClassName: String = ''; hInst: HMODULE = 0): Boolean;
    procedure DeactiveW2W;
    function ConnectWnd(hConnWnd: HWND): Boolean;
    function ActiveNp(sPipeName: String; bNpServer: Boolean): Boolean; virtual;
    function ConnectNp: Boolean;
    procedure DeactiveNp;

    procedure Disconnect;

    property Host: String read sHost_ write SetHost;
    property Port: Integer read nPort_ write SetPort;
    property Connected: Boolean read GetConnected write SetConnected;
    property TryReconnect: Boolean read bTryReconnect_ write SetTryReconnect;
    property ConnDT: TDateTime read dtConn_;
  end;

implementation

uses
  IdStack, IdGlobal, IdException, IdExceptionCore, Tocsg.PacketDefine,
  Tocsg.WinInfo, Tocsg.DateTime, Tocsg.Network, Tocsg.WTS, Tocsg.Process, Define;

function PosBin(const pFind, pDestBuf: TBytes; nBeginOffset: Integer = 0): Integer;
var
  i, j, lp, ld: integer;
begin
  lp := Length(pFind);
  ld := Length(pDestBuf);
  Result := -1;
  if (lp > ld) or (nBeginOffset >= ld) then
    Exit;

  for i := nBeginOffset to ld-lp-1 do
  begin
    for j := 0 to lp -1 do
    begin
      if pFind[j] <> pDestBuf[i + j] then
        Break;

      if j = lp-1 then
        Result := i;
    end;

    if Result <> -1 then
      Break;
  end;
end;

{ TTgClientBase }

Constructor TTgClientBase.Create(const sHost: String; nPort: Integer;
          nReConnTerm: Integer = 5000; nPingTerm: Integer = 60000);
begin
  CS_ := TCriticalSection.Create;
  Inherited Create;

  PktEncKind_ := ekNone;
  nSvrPktVer_ := 0;
  bSendPause_ := true;
  nReConnTerm_ := nReConnTerm;
  if nReConnTerm_ < 1000 then
    nReConnTerm_ := 1000;
  nPingTerm_ := nPingTerm;
//  if nPingTerm_ < 60000 then
//    nPingTerm_ := 60000;

  Enc_ := nil;
  W2W_ := nil;
  hIpcWnd_ := 0;
  NpIpc_ := nil;

  _DcRcvBufs := TDictionary<LONGLONG,PClientRcvPacket>.Create;
  _DcRcvBufs.OnValueNotify := OnRcvBufNotify;
  _pRcvPkt := nil;

  SetLength(_pInitBuf, 0);

  sHost_ := sHost;
  nPort_ := nPort;

  dwPingTick_ := 0;
  dwDisconnTick_ := 0;
  dtConn_ := 0;
  bConnected_ := false;
  bTryReconnect_ := false;

  QSendPacket_ := TQueue<ISendPacket>.Create;

  if nPort <> -1 then
  begin
    Client_ := TIdTCPClient.Create(nil);
    Client_.OnConnected := _ConnectedEvent;
    Client_.OnDisconnected := _DisconnectedEvent;
    Client_.ConnectTimeout := 10000;
    Client_.ReadTimeout := 20000;
  end else
    Client_ := nil;

  StartThread;
end;

Destructor TTgClientBase.Destroy;
begin
  Inherited;
  DeactiveW2W;
  DeactiveNp;
  if Client_ <> nil then
    FreeAndNil(Client_);
  FreeAndNil(QSendPacket_);
  FreeAndNil(_DcRcvBufs);
  if Assigned(Enc_) then
    FreeAndNil(Enc_);
  FreeAndNil(CS_);
end;

procedure TTgClientBase.OnRcvBufNotify(Sender: TObject; const Item: PClientRcvPacket; Action: TCollectionNotification);
begin
  case Action of
    cnAdded: ;
    cnRemoved:
    begin
      if Item.pBuf <> nil then
        FreeMem(Item.pBuf);
      Dispose(Item);
    end;
    cnExtracted: ;
  end;
end;

procedure TTgClientBase.Lock;
begin
  CS_.Acquire;
end;

procedure TTgClientBase.Unlock;
begin
  CS_.Release;
end;

function TTgClientBase.GetSendPauseState: Boolean;
begin
  Lock;
  try
    Result := bSendPause_;
  finally
    Unlock;
  end;
end;

procedure TTgClientBase.SetSendPauseState(bVal: Boolean);
begin
  Lock;
  try
    bSendPause_ := bVal;
  finally
    Unlock;
  end;
end;

procedure TTgClientBase.OnW2WConnection(Sender: TTgObject; aState: TW2wConnState; hRcvWnd: HWND);
begin
  if W2W_ <> nil then
  begin
    try
      case aState of
        wcsConnect :
        begin
          if (hIpcWnd_ <> 0) and (hIpcWnd_ <> hRcvWnd) then
            Disconnect;

          hIpcWnd_ := hRcvWnd;
//          _Trace('W2W - OnW2WConnection(), hIpcWnd_=%d, PID=%d', [hIpcWnd_, GetProcessPIDFromWndHandle(hIpcWnd_)]);
          if hIpcWnd_ <> 0 then
            _ConnectedEvent(nil);
        end;
        wcsDisconnect :
        begin
          _DisconnectedEvent(nil);
          hIpcWnd_ := 0;
          W2W_.ClearQueue;
        end;
      end;
    except
      on E: Exception do
        ETgException.TraceException(Self, E, 'Fail .. OnW2WConnection()');
    end;
  end;
end;

procedure TTgClientBase.OnNpConnected(Sender: TTgNpBase; hPipe: THandle);
begin
  _ConnectedEvent(nil);
end;

procedure TTgClientBase.OnNpDisconnected(Sender: TTgNpBase; hPipe: THandle);
begin
  _DisconnectedEvent(nil);
end;

procedure TTgClientBase._ConnectedEvent(Sender: TObject);
begin
  SetPacketEncryptInfo(ekNone, '');
  dwPingTick_ := GetTickCount;
  _Trace('ConnectedEvent()', 3);

  if Client_ <> nil then
    SetHost(Client_.Host);

  if Client_ <> nil then
    SetPort(Client_.Port);
  SetConnected(true);

  ConnectedEvent;
end;

procedure TTgClientBase._DisconnectedEvent(Sender: TObject);
begin
  dwDisconnTick_ := GetTickCount;
  _Trace('DisconnectedEvent()', 3);
  SetConnected(false);
  DisconnectedEvent;
  SetPacketEncryptInfo(ekNone, '');
end;

function TTgClientBase.GetPktEncKind: TTgEncKind;
begin
  Lock;
  try
    Result := PktEncKind_;
  finally
    Unlock;
  end;
end;

function TTgClientBase.GetConnected: Boolean;
begin
  Lock;
  try
    Result := bConnected_;
  finally
    Unlock;
  end;
end;

procedure TTgClientBase.SetConnected(const bVal: Boolean);
begin
  Lock;
  try
    if bConnected_ <> bVal then
    begin
      bConnected_ := bVal;
      if bVal then
        dtConn_ := Now
      else
        dtConn_ := 0;
    end;
  finally
    Unlock;
  end;
end;

procedure TTgClientBase.SetHost(const sHost: String);
begin
  if not GetConnected then
  begin
    if sHost_ <> sHost then
    begin
      sHost_ := sHost;
    end;
  end else
    raise ETgClient.Create('접속중일 때에는 IP를 변경 할 수 없습니다.');
end;

procedure TTgClientBase.SetPort(const nPort: Integer);
begin
  if not GetConnected then
  begin
    if nPort_ <> nPort then
    begin
      nPort_ := nPort;
    end;
  end else
    raise ETgClient.Create('접속중일 때에는 Port를 변경 할 수 없습니다.');
end;

function TTgClientBase.GetTryReconnect: Boolean;
begin
  Lock;
  try
    Result := bTryReconnect_;
  finally
    Unlock;
  end;
end;

procedure TTgClientBase.SetTryReconnect(const bVal: Boolean);
begin
  Lock;
  try
    if bTryReconnect_ <> bVal then
      bTryReconnect_ := bVal;
  finally
    Unlock;
  end;
end;

procedure TTgClientBase.SetPacketEncryptInfo(aPktEncKind: TTgEncKind; const sPktEncPass: String);
begin
  Lock;
  try
    PktEncKind_ := aPktEncKind;
    PktEncPass_ := sPktEncPass;

    if Assigned(Enc_) then
      FreeAndNil(Enc_);

    if PktEncKind_ <> ekNone then
      Enc_ := TTgEncrypt.Create(sPktEncPass, PktEncKind_);
  finally
    Unlock;
  end;
end;

procedure TTgClientBase.ConnectedEvent;
var
  Send: ISendPacket;
begin
  Send := TTgPacket.Create(TOC_CLIENT_INFO);

  with Send do
  begin
    I['Type'] := CLIENT_TYPE;
    S['Ver'] := CLIENT_VER;
    S['ComName'] := GetComName;
    S['Account'] := WTS_GetCurrentUserName;
    S['WinVer'] := GetWinVer;
    D['BootDT'] := GetBootTime;
    S['rIP'] := GetHostIP;
    S['IpAddrs'] := GetIPAddrsToCommaStrEx;
    S['MacAddr'] := GetMACAddrUsing;
    S['MacAddrs'] := GetMACAddrToCommaStr;
    D['CltDT'] := Now;
  end;

  Sleep(500);
  DirectSendPacket(Send);
  _Trace('Send Client Info.', 3);
end;

procedure TTgClientBase.DisconnectedEvent;
begin
  SetSendPauseState(true);
end;

procedure TTgClientBase.ProcessRcvPacket(aRcv: IRcvPacket);

  procedure process_QTC_CONFIRM_PACKET_ENCRYPT;
  var
    PktEncKind: TTgEncKind;
    Send: ISendPacket;
  begin
    PktEncKind := TTgEncKind(aRcv.I['K']);
    nSvrPktVer_ := aRcv.I['SPV'];
    case PktEncKind of
      ekAes256cbc,
      ekAes192cbc,
      ekAes128cbc : ;  // 지원
      else
      begin
        PktEncKind := ekNone;
      end;
    end;

    Send := TTgPacket.Create(aRcv, pkCritical);
    Send.I['K'] := LONGLONG(PktEncKind);
    Send.I['CPV'] := PACKET_VERSION;
    DirectSendPacket(Send);
  end;

  procedure process_QTC_UPDATE_PACKET_ENCRYPT;
  var
    PktEncKind: TTgEncKind;
    sPass: String;
    bResult: Boolean;
    nLen: Integer;
    Send: ISendPacket;
  begin
//    _Trace('process_QTC_UPDATE_PACKET_ENCRYPT');
    bResult := false;
    PktEncKind := TTgEncKind(aRcv.I['K']);
    sPass := aRcv.S['J'];
    try
      case PktEncKind of
        ekAes256cbc,
        ekAes192cbc,
        ekAes128cbc :
        begin
          sPass := DecBinStrToStr(PktEncKind, ENC_PASSPASS, sPass);
          nLen := Length(sPass);
          if nLen > 7 then
            sPass := Copy(sPass, 4, nLen - 7)
          else exit;
        end
        else sPass := '';
      end;
      bResult := true;
    finally
      if (sPass <> '') and bResult then
      begin
      // 성공했다면 서버에 통보 14_0703 16:23:49 sunk
        Send := TTgPacket.Create(TOC_UPDATE_PACKET_ENCRYPT, pkCritical); // 중요 패킷 구분 14_0704 16:52:06 sunk
        DirectSendPacket(Send);
        SetPacketEncryptInfo(PktEncKind, sPass);
      end;

    // 암호화 정보 송수신 완료전까지 다른 패킷 전송 금지 18_0117 14:45:40 sunk
      SetSendPauseState(false);
    end;
  end;

begin
  try
    case aRcv.Command of
      TOC_CONFIRM_PACKET_ENCRYPT : process_QTC_CONFIRM_PACKET_ENCRYPT;
      TOC_UPDATE_PACKET_ENCRYPT  : process_QTC_UPDATE_PACKET_ENCRYPT;
      TOC_PING :
      begin
        if not GetSendPauseState then
        begin
        // 핑 받을때까지 전송 대기라면... 패킷 암호화 설정 정보를 못 받았다고 판단.. 다시 요청한다. 22_0412 11:00:43 kku
          _Trace('CRM_PING .. PauseState true?? ..  Request Sv-SendPacketEncConfirm()');
          SendPacket(TTgPacket.Create(TOC_REQUEST_CONFIRM_PACKET_ENCRYPT));
        end;
      end;
    end;
  except
    on E: Exception do
      ETgClient.TraceException(Self, E, 'Fail .. ProcessRcvPacket()');
  end;
end;

procedure TTgClientBase.SendPacket(Send: ISendPacket);
begin
  if GetConnected then
  begin
    Lock;
    try
      QSendPacket_.Enqueue(Send);
    finally
      Unlock;
    end;
  end else
    _ProcessSendFail(Send);
end;

function TTgClientBase.DequeueSendPacket: ISendPacket;
begin
  Lock;
  try
    if not bSendPause_ and
       (QSendPacket_.Count > 0) then
      Result := QSendPacket_.Dequeue
    else
      Result := nil;
  finally
    Unlock;
  end;
end;

procedure TTgClientBase.DirectSendPacket(Send: ISendPacket);
begin
  try
    _ProcessSend(Send);
  except
    on E: Exception do
      ETgException.TraceException(Self, E, 'Fail .. DirectSendPacket()');
  end;
end;

function TTgClientBase.Connect(const sHost: String; nPort: Integer): Boolean;
var
  nOldCTO,
  nOldRTO: Integer;
begin
  if Client_ = nil then
  begin
    Result := false;
    exit;
  end;

  Result := true;

  if not GetConnected then
  begin
    _DcRcvBufs.Clear;
    _pRcvPkt := nil;
    SetLength(_pInitBuf, 0);

    SetLength(_RcylBuf, 0);
    _nRcylBufLen := 0;
    _nRcylBufPos := 0;

    with Client_ do
    begin
      PktEncKind_ := ekNone;

      nSvrPktVer_ := 0;
      Host := sHost;
      Port := nPort;

      nOldCTO := ConnectTimeout;
      nOldRTO := ReadTimeout;
      ConnectTimeout := 20000;
      ReadTimeout := 30000;
      try
        try
          Client_.Connect;

          Socket.RecvBufferSize := 32 * 1024;
          Socket.SendBufferSize := 32 * 1024;

          Result := true;
          SetConnected(true);
        except
          on E: EIdSocketError do
          begin
            if E.LastError <> 10061 then // 서버 활성화 안됐을때 접속 시도 시 이게 계속 로그에 찍혀서 제외처리
              ETgException.TraceException(Self, E, 'Fail .. Connect()');
            Result := false;
            SetConnected(false);
            exit;
          end;

          on E: Exception do
          begin
            ETgClient.TraceException(Self, E, 'Fail .. Connect()');
            Result := false;
            SetConnected(false);
            exit;
          end;
        end;
      finally
        ReadTimeout := nOldRTO;
        ConnectTimeout := nOldCTO;
      end;
    end;
  end;
end;

function TTgClientBase.Connect: Boolean;
begin
  if (sHost_ <> '') and (nPort_ <> 0) then
    Result := Connect(sHost_, nPort_)
  else
    Result := false;
end;

function TTgClientBase.GetSelfWnd: HWND;
begin
  if W2W_ <> nil then
    Result := W2W_.RcWnd
  else
    Result := 0;
end;

function TTgClientBase.ActiveW2W(sClassName: String = ''; hInst: HMODULE = 0): Boolean;
begin
  if W2W_ = nil then
  begin
    W2W_ := TTgWnd2Wnd.Create(sClassName, hInst);
    W2W_.OnW2WConnection := OnW2WConnection;
    Result := true;
  end else
    Result := false;
end;

procedure TTgClientBase.DeactiveW2W;
begin
  if W2W_ <> nil then
  begin
    Disconnect;
    FreeAndNil(W2W_);
  end;
end;

function TTgClientBase.ConnectWnd(hConnWnd: HWND): Boolean;
begin
  Result := false;
  if not GetConnected and (W2W_ <> nil) then
  begin
    try
      if (hConnWnd <> 0) and (hConnWnd <> hIpcWnd_) then
      begin
        Result := SendMessage(hConnWnd, WM_WND_HANDSHAKE, NativeUInt(wcsConnect), W2W_.RcWnd) = WM_WND_HANDSHAKE;

        if Result then
        begin
          hIpcWnd_ := hConnWnd;
          _ConnectedEvent(nil);
        end
      {$IFDEF DEBUG}
        else
          _Trace('Fail .. W2W - ConnectWnd() .. SendData()');
      {$ENDIF};
      end;
    except
      on E: EXception do
        ETgException.TraceException(Self, E, 'Fail .. W2W - ConnectWnd()');
    end;
  end;
end;

function TTgClientBase.ActiveNp(sPipeName: String; bNpServer: Boolean): Boolean;
begin
  if NpIpc_ = nil then
  begin
    if bNpServer then
    begin
      NpIpc_ := TTgNpServer.Create(sPipeName);
      SetSendPauseState(false);
    end else begin
      NpIpc_ := TTgNpClient.Create(sPipeName);
      NpIpc_.OnConnected := OnNpConnected;
      NpIpc_.OnDisconnected := OnNpDisconnected;
    end;
    Result := true;
  end else
    Result := false;
end;

function TTgClientBase.ConnectNp: Boolean;
begin
  Result := false;
  if not GetConnected and (NpIpc_ <> nil) then
  begin
    try
      if NpIpc_.IsServer then
      begin
        Result := TTgNpServer(NpIpc_).Listen;
        SetConnected(Result);
      end else begin
        Result := TTgNpClient(NpIpc_).Connect;
        if Result then
          _ConnectedEvent(nil);
      end;
    except
      on E: EXception do
        ETgException.TraceException(Self, E, 'Fail .. NpIpc - ConnectWnd()');
    end;
  end;
end;

procedure TTgClientBase.DeactiveNp;
begin
  if NpIpc_ <> nil then
    FreeAndNil(NpIpc_);
end;

procedure TTgClientBase.Disconnect;
begin
  try
    _Trace('Disconnect()', 3);
    SetConnected(false);
    if W2W_ <> nil then
    begin
      W2W_.ClearQueue;
      if hIpcWnd_ <> 0 then
      begin
        _DisconnectedEvent(nil);
        SendMessage(hIpcWnd_, WM_WND_HANDSHAKE, NativeUInt(wcsDisconnect), W2W_.RcWnd);
        hIpcWnd_ := 0;
      end;
    end else
    if NpIpc_ <> nil then
    begin
      if NpIpc_.IsServer then
      begin
        TTgNpServer(NpIpc_).Close;
      end else begin
        TTgNpClient(NpIpc_).Disconnect;
        _DisconnectedEvent(nil);
      end;
    end else begin
      if Client_.Socket <> nil then
      begin
        Client_.Socket.InputBuffer.Clear;
        Client_.Socket.CloseGracefully;
      end;
      Client_.Disconnect;

      _DcRcvBufs.Clear;
      _pRcvPkt := nil;

      _DisconnectedEvent(Client_);
    end;
  except
  //
  end;
end;

function TTgClientBase._ProcessRcv: Boolean;

  procedure PK_ReadBytes(var aBuffer: TBytes; nCount: Integer);
  var
    nLen, nRead,
    nNewPktSigPos: Integer;
  {$IFDEF Win64}
    arrSig: TBytes;
  {$ENDIF}
  begin
    if _nRcylBufLen > _nRcylBufPos then
    begin
    // 20200131TEST!!
//      _Trace('PK_ReadBytes .. _RcylBuf Read~');
      nLen := Length(aBuffer);
      if (_nRcylBufLen - _nRcylBufPos) > nCount then
        nRead := nCount
      else
        nRead := _nRcylBufLen - _nRcylBufPos;

      SetLength(aBuffer, nLen + nRead);
      CopyMemory(@aBuffer[nLen], @_RcylBuf[_nRcylBufPos], nRead);
      Inc(_nRcylBufPos, nRead);
    end else
      Client_.Socket.ReadBytes(TIdBytes(aBuffer), nCount, true);

    if (_nRcylBufLen > 0) and (_nRcylBufLen <= _nRcylBufPos) then
    begin
    // 20200131TEST!!
//      _Trace('PK_ReadBytes .. _RcylBuf Clear~~~~~');
      _nRcylBufLen := 0;
      _nRcylBufPos := 0;
      SetLength(_RcylBuf, 0);
    end;

  // 읽은 버퍼 뒤에 새로운 패킷 시작이 있는지 확인 20_0131 15:17:22 sunk
  {$IFDEF Win64}
  // 64에서 이렇게 안하면 Range 오류나서 위 코드 추가 (11.1) 22_1208 09:22:00 kku
    SetLength(arrSig, LEN_CTX_PACKET_SIGNATURE);
    CopyMemory(arrSig, @CTX_PACKET_SIGNATURE[1], LEN_CTX_PACKET_SIGNATURE);
    nNewPktSigPos := PosBin(arrSig, aBuffer, 1 {SearchBeginOffset});
  {$ELSE}
    nNewPktSigPos := PosBin(TBytes(@CTX_PACKET_SIGNATURE[1]), aBuffer, 1 {SearchBeginOffset});
  {$ENDIF}
    if nNewPktSigPos > 0 then
    begin
    // 20200131TEST!!
//      _Trace('PK_ReadBytes .. Found NewPktSigPos~');
      nLen := Length(aBuffer);
      if _nRcylBufLen = 0 then
      begin
        _nRcylBufLen := nLen - nNewPktSigPos;
        SetLength(_RcylBuf, _nRcylBufLen);
        CopyMemory(@_RcylBuf[0], @aBuffer[nNewPktSigPos], _nRcylBufLen);
        SetLength(aBuffer, nLen - _nRcylBufLen);
      end else begin
        Dec(_nRcylBufPos, nLen - nNewPktSigPos);
        SetLength(aBuffer, nLen - (nLen - nNewPktSigPos));
      end;
    end;
  end;

var
  pDecBuf,
  pTempBuf: TBytes;
  nInitBufLen,
  nBufLen, nDecLen: Integer;
  PktEncKind: TTgEncKind;
  Rcv: IRcvPacket;
{$IFDEF _ENC_TEST_}
  sPath,
  sAlgo: String;
  sData: AnsiString;
  fs: TFileStream;
{$ENDIF}
  pData: PWndDataEnt;
Label
  LB_READ_BYTES;
begin
  Result := false;

  if W2W_ <> nil then
  begin
    try
      pData := W2W_.DeququeData;
      if pData = nil then
        exit;

      if pData.llSender <> hIpcWnd_ then
      begin
        _Trace('Change Sender HWND ..');
        hIpcWnd_ := pData.llSender;
        exit;
      end;

//      Rcv := TTgPacket.Create(Copy(PChar(pData.pBuf), 1, pData.dwLen));
      Rcv := TTgPacket.Create(Copy(PChar(pData.pBuf), 1, (pData.dwLen div 2) - 1));
      FreeMem(pData.pBuf, pData.dwLen);
      Dispose(pData);

      ProcessRcvPacket(Rcv);
      Result := true;
    except
      on E: Exception do
        ETgException.TraceException(Self, E, 'Fail .. W2W - _ProcessRcv()');
    end;
  end else
  if NpIpc_ <> nil then
  begin
    try
      if NpIpc_.RcvData(pTempBuf) > 0 then
      begin
        Rcv := TTgPacket.Create(pTempBuf, true);
        if NpIpc_.IsServer then
          TTgPacket(Rcv).Toss := TTgNpServer(NpIpc_).LastRcvPipe;
        ProcessRcvPacket(Rcv);
        Result := true;
      end;
    except
      on E: Exception do
        ETgException.TraceException(Self, E, 'Fail .. NpIPC - _ProcessRcv()');
    end;
  end else begin
    if Client_ = nil then
      exit;

    try
      if not Client_.Socket.Readable(1) and
         (Client_.Socket.InputBuffer.Size = 0) then exit;

      if (_pRcvPkt = nil) or (_pRcvPkt.llId <> -1) then
      begin
      // 이전 버전이랑 구분하기 위해서 만들었는 위치가 좋지 않지만 일단 이렇게 활용한다. 18_0411 16:08:51 sunk

      // 암호화 지원 확인 전까진 최신 버전도 (sServerVer_ = '') 상태이긴 한데...
      // 32K 미만이면 괜찮고 암호화 지원 확인 전까진 32K 이상 패킷이 없기 때문에...
      // 일단 이렇게 간다...아아아아앙앙ㅇㅇㅇ아ㅏㅏㅏㅏ 18_0411 17:40:50 sunk
  //      if sServerVer_ <> '' then

      // 서버에서도 패킷 버전을 통보하여 해당 정보를 통해 별도 식별 후 처리 18_0423 10:44:29 sunk
  //      if nSvrPktVer_ >= 4 then
        begin
        // 서버에서 32k 단위로 헤더 달아서 쪼개서 보내는걸 그냥 모아서 보내는걸로 수정했다.
        // 따라서 클라이언트 단에서 쪼개서 받을때 무조건 헤드를 검사하면 안된다.
          if (_pRcvPkt <> nil) and (_pRcvPkt.llId <> -1) then
          begin
          // 헤더 확인 안하고 이어서 받기 처리 추가 18_0411 15:21:28 sunk
            if _DcRcvBufs.ContainsKey(_pRcvPkt.llId) then
            begin
              _pRcvPkt := _DcRcvBufs[_pRcvPkt.llId];
              if _pRcvPkt.nRemainPktLen > 0 then
                goto LB_READ_BYTES;
            end;
          end;
        end;

        if Length(_pInitBuf) = 0 then
          PK_ReadBytes(_pInitBuf, 6)
        else
          PK_ReadBytes(_pInitBuf, 1);

      // 6 이 아니면... 여기서 통신 먹통되는 현상 확인 20_0131 16:30:11 sunk
      // _pInitBuf 이거 재활용 하면서 6 이상이 되는 경우가 종종 발생한다.
  //        if Length(_pInitBuf) <> 6 then
        nInitBufLen := Length(_pInitBuf);
        if nInitBufLen < 6 then // 6 이하면 넘기는걸로 수정 20_0131 16:30:26 sunk
          exit;

      // ------------- 주의 ----------------------------------------------------
      // 현재 서버에서 데이터를 큰 패킷단위로 쪼개서 보낼때....
      // 중간중간 작은 패킷이 중간에 껴서 오는 문제가 있다...
      // 서버 워크스레드로 동시에 보내서 발생하는 문제인것 같은데...
      //
      // 패킷 조각이 좀 크면 그 조각 데이터 사이에 작은 별도패킷이 끼고
      // 다른 패킷 조각이 새로 새치기 되고... 등등
      // 서버에서 패킷 전송 시 하나의 스레드로 보내도록 하는 방법밖에 없을거 같긴하다.
      // 일단 그냥 이대로 쓰는걸로... 20_0203 14:06:47 sunk
      // -----------------------------------------------------------------------

        if CompareMem(@_pInitBuf[0], @CTX_PACKET_SIGNATURE[1], 6) then
        begin
          if nInitBufLen < LEN_CTX_PACKET_HEADER then
            PK_ReadBytes(_pInitBuf, LEN_CTX_PACKET_HEADER - nInitBufLen)
          else
            _Trace('No~~~ Don''t read CTX_PACKET_HEADER');

          if not _DcRcvBufs.ContainsKey(PCtxPacketHeader(_pInitBuf).dwId) then
          begin
            New(_pRcvPkt);
            ZeroMemory(_pRcvPkt, SizeOf(TClientRcvPacket));
            _pRcvPkt.wRank := PCtxPacketHeader(_pInitBuf).wRank;
            _pRcvPkt.llId := PCtxPacketHeader(_pInitBuf).dwId;
            _pRcvPkt.nRemainPktLen := PCtxPacketHeader(_pInitBuf).dwSize;
            _pRcvPkt.pBuf := AllocMem(_pRcvPkt.nRemainPktLen);
            _DcRcvBufs.Add(_pRcvPkt.llId, _pRcvPkt);
          end else
            _pRcvPkt := _DcRcvBufs[PCtxPacketHeader(_pInitBuf).dwId];

          SetLength(_pInitBuf, 0);
        end else begin
          if _pRcvPkt = nil then
          begin
            _pInitBuf[0] := _pInitBuf[1];
            _pInitBuf[1] := _pInitBuf[2];
            _pInitBuf[2] := _pInitBuf[3];
            _pInitBuf[3] := _pInitBuf[4];
            _pInitBuf[4] := _pInitBuf[5];
            SetLength(_pInitBuf, 5);
            Result := true;
            exit;
          end else
          if _pRcvPkt.nRemainPktLen > 0 then
          begin
            nBufLen := Length(_pInitBuf);

  //            SetLength(pTempBuf, nBufLen);
  //            CopyMemory(@pTempBuf[0], @_pInitBuf[0], nBufLen);

            CopyMemory(Pointer(NativeInt(_pRcvPkt.pBuf)+_pRcvPkt.nProcPktLen), @_pInitBuf[0], nBufLen);
            Inc(_pRcvPkt.nProcPktLen, nBufLen);
            Dec(_pRcvPkt.nRemainPktLen, nBufLen);

            SetLength(_pInitBuf, 0);
            SetLength(pTempBuf, 0);

            goto LB_READ_BYTES;
          end else begin
            _DcRcvBufs.Remove(_pRcvPkt.llId);
            _pRcvPkt := nil;
          end;
        end;

        if (_pRcvPkt = nil) or (_pRcvPkt.nRemainPktLen = 0) then
        begin
          exit;
        end;

        SetLength(pTempBuf, 0);

      LB_READ_BYTES :
  //      if not Client_.Socket.Readable(1) and
  //         (Client_.Socket.InputBuffer.Size = 0) then exit;

        if _pRcvPkt.nRemainPktLen > BUFFER_SIZE then // TBytes 최대길이가 131071을 넘지 못해서 이렇게 잘라서 처리하도록 한다. 14_0610 17:31:00 sunk
          PK_ReadBytes(pTempBuf, BUFFER_SIZE)
        else
          PK_ReadBytes(pTempBuf, _pRcvPkt.nRemainPktLen);
        nBufLen := Length(pTempBuf);

        if nBufLen > 0 then
        begin
          CopyMemory(Pointer(NativeInt(_pRcvPkt.pBuf)+_pRcvPkt.nProcPktLen), @pTempBuf[0], nBufLen);
          Inc(_pRcvPkt.nProcPktLen, nBufLen);
          Dec(_pRcvPkt.nRemainPktLen, nBufLen);
        end;
      end else begin
      // 기존 패킷 처리 14_0610 14:42:13 sunk
        if _pRcvPkt.nRemainPktLen > BUFFER_SIZE then // TBytes 최대길이가 131071을 넘지 못해서 이렇게 잘라서 처리하도록 한다. 14_0610 17:31:00 sunk
          PK_ReadBytes(pTempBuf, BUFFER_SIZE)
        else
          PK_ReadBytes(pTempBuf, _pRcvPkt.nRemainPktLen);
        nBufLen := Length(pTempBuf);

        if nBufLen > 0 then
        begin
          CopyMemory(Pointer(NativeInt(_pRcvPkt.pBuf)+_pRcvPkt.nProcPktLen), @pTempBuf[0], nBufLen);
          Inc(_pRcvPkt.nProcPktLen, nBufLen);
          Dec(_pRcvPkt.nRemainPktLen, nBufLen);
        end;
      end;

      Result := true;

    // 20200131TEST!!
  //      if _pRcvPkt.nRemainPktLen < 0 then
  //        _pRcvPkt.nRemainPktLen := _pRcvPkt.nRemainPktLen + 0;

      if (_pRcvPkt <> nil) and (_pRcvPkt.nRemainPktLen = 0) then
      begin
      // 20200131TEST!!
  //        if _nRcylBufLen > 0 then
  //          _Trace('MakePacket ... _nRcylBufLen = %d, _nRcylBufPos = %d', [_nRcylBufLen, _nRcylBufPos]);

      // 암호화 패킷 처리 추가 14_0704 10:15:21 sunk
        PktEncKind := GetPktEncKind;
        if PktEncKind <> ekNone then
        begin
          Lock;
          try
            ASSERT(Enc_ <> nil, 'none.. encrypt obj ..');
            pDecBuf := Enc_.DecryptBufferEx(_pRcvPkt.pBuf, _pRcvPkt.nProcPktLen);
          finally
            Unlock;
          end;

          case PktEncKind of
            ekAes256cbc,
            ekAes192cbc,
            ekAes128cbc:
            begin
            // 언패딩이 필요한 알고리즘의 경우,
            // 언패딩 후 길이를 다시 계산. 18_0411 10:08:16 sunk
              nDecLen := Length(pDecBuf);
            end;
            else
              nDecLen := _pRcvPkt.nProcPktLen;
          end;

  //        if (nSvrPktVer_ < 4) and (nDecLen > 5) and
  //           (pDecBuf[0] <> 123) and (pDecBuf[1] <> 34) and
  //           (pDecBuf[4] = 123) and (pDecBuf[5] = 34) then     // 올드 서버 패킷 체크 ....
  //          Rcv := TTgPacket.Create(@pDecBuf[4], nDecLen-4)
  //        else
            Rcv := TTgPacket.Create(@pDecBuf[0], nDecLen);
        end else begin
  //        if (nSvrPktVer_ < 4) and (_pRcvPkt.nProcPktLen > 5) and
  //           (TBytes(_pRcvPkt.pBuf)[0] <> 123) and (TBytes(_pRcvPkt.pBuf)[1] <> 34) and
  //           (TBytes(_pRcvPkt.pBuf)[4] = 123) and (TBytes(_pRcvPkt.pBuf)[5] = 34) then  // 올드 서버 패킷 체크 ....
  //        begin
  //        // 서버 버전이 표시 되지 않는 옛날 서버의 경우 (AES 알고리즘 추가전)
  //        // 패킷 버전이 1,2가 이닌 3인걸 보고 데이터 퍼버 앞 4바이트에 크기 정보도 함께 보내준다.
  //        // 그래서 앞에 4바이트 잘라서 처리해줘야한다. 18_0411 17:14:33 sunk
  //          Rcv := TTgPacket.Create(Pointer(NativeInt(_pRcvPkt.pBuf)+4), _pRcvPkt.nProcPktLen-4);
  //        end else
            Rcv := TTgPacket.Create(_pRcvPkt.pBuf, _pRcvPkt.nProcPktLen);
        end;

      {$IFDEF _ENC_TEST_}
        case Rcv.Command of
          1001 {QTC_CLIENT_INFO},
          1111 {QTC_COLLECTOR_ENTER},
          3101 {QTC_REQUEST_AGENT_LIST},
          3406 {QTC_REQUEST_PI_DRIVELIST} :
          begin
            case PktEncKind of
              ekNone      : sAlgo := 'None';
              ekAes256cbc : sAlgo := 'AES256CBC';
              ekAes192cbc : sAlgo := 'AES192CBC';
              ekAes128cbc : sAlgo := 'AES128CBC';
              else
                sAlgo := 'Unknown';
            end;
            sPath := ExtractFileDrive(GetCurrentPath) + '\QT_ENCTEST\';
            if ForceDirectories(sPath) then
            begin
              sPath := sPath + Format('R-%s-TYP=%d-%s-Cmd=%d.pkt', [FormatDateTime('yyyymmddhhnnss', Now), CLIENT_TYPE, sAlgo, Rcv.Command]);
              fs := TFileStream.Create(sPath, fmCreate);
              try
                if PktEncKind <> ekNone then
                begin
                  sData := EncodeBase64(_pRcvPkt.pBuf, _pRcvPkt.nProcPktLen);
                  fs.Write(@sData[1], Length(sData));
                end else
                  fs.Write(_pRcvPkt.pBuf^, _pRcvPkt.nProcPktLen);
              finally
                fs.Free;
              end;
            end;
          end;
        end;
      {$ENDIF}

        ProcessRcvPacket(Rcv);

        if _pRcvPkt <> nil then // ProcessRcvPacket() 처리중에 Disconnect()가 올수도 있다.
        begin
          _DcRcvBufs.Remove(_pRcvPkt.llId);
          _pRcvPkt := nil;
        end;
      end;

    except
      on e: EIdReadTimeout do
      begin
        ETgException.TraceException(Self, E, 'Fail.. _ProcessRcv()..');
        exit;
      end;

      on e: ETgPacket do
      begin
        ETgException.TraceException(Self, E, '_ProcessRcv() .. Packet error ...');
        if _pRcvPkt <> nil then
        begin
          _DcRcvBufs.Remove(_pRcvPkt.llId);
          _pRcvPkt := nil;
        end;
        ETgException.TraceException(Self, E, 'Fail.. _ProcessRcv()..');
      end;

      on e: Exception do
      begin
        if _pRcvPkt <> nil then
        begin
          _DcRcvBufs.Remove(_pRcvPkt.llId);
          _pRcvPkt := nil;
        end;
        ETgException.TraceException(Self, E, 'Fail.. _ProcessRcv()..');
        raise;
      end;
    end;
  end;
end;

function TTgClientBase._ProcessSend(Send: ISendPacket): Boolean;
var
  pBuf,
  pSendBuf: TBytes;
  nLen, nTotalLen: Integer;
  CtxPacketHeader: TCtxPacketHeader;
  PktEncKind: TTgEncKind;
{$IFDEF _ENC_TEST_}
  sPath,
  sAlgo: String;
  sData: AnsiString;
  fs: TFileStream;
{$ENDIF}

  procedure ProcessEncrypt;
  var
    pData,
    pEncData: TBytes;
  begin
    nLen := Send.ToBytesDataOnly(pData);
    if nLen > 0 then
    begin
      Lock;
      try
      {$IFDEF DEBUG}
        ASSERT(Enc_ <> nil, 'none.. encrypt obj ..');
      {$ENDIF}

        pEncData := Enc_.EncryptBufferEx(@pData[0], nLen);
        case PktEncKind of
          ekAes256cbc,
          ekAes192cbc,
          ekAes128cbc:
          begin
          // 패딩이 필요한 알고리즘의 경우,
          // 패딩 길이도 들어가야해서 다시 계산. 18_0411 10:08:16 sunk
            nLen := Length(pEncData);
          end;
        end;
      finally
        Unlock;
      end;
      SetLength(pBuf, nLen);
      CopyMemory(@pBuf[0], @pEncData[0], nLen);
    end;
  end;

begin
  Result := false;

  if Send = nil then
    exit;

  if W2W_ <> nil then
  begin
    try
      if hIpcWnd_ <> 0 then
      begin
//        _Trace('W2W - SendData(), hIpcWnd_=%d, PID=%d', [hIpcWnd_, GetProcessPIDFromWndHandle(hIpcWnd_)]);
        Result := W2W_.SendData(hIpcWnd_, Send);
        if not Result then
        begin
          _Trace('Fail .. W2W - SendData()');
          Disconnect;
        end;
      end;
    except
      on E: EXception do
        ETgException.TraceException(Self, E, 'Fail .. W2W - _ProcessSend()');
    end;
  end else
  if NpIpc_ <> nil then
  begin
    try
      Result := NpIpc_.SendData(Send);
    except
      on E: EXception do
        ETgException.TraceException(Self, E, 'Fail .. NpIPC - _ProcessSend()');
    end;
  end else begin
    if Client_ = nil then
      exit;

    try
      Lock;
      try
        PktEncKind := GetPktEncKind;
  //        _Trace('_ProcessSend() .. PktEncKind = %d', [Integer(PktEncKind)]);
        if PktEncKind = ekNone then
          nLen := Send.ToBytesDataOnly(pBuf)
  //          nLen := Send.ToBytes(pBuf)
        else
          ProcessEncrypt;

      {$IFDEF _ENC_TEST_}
        case Send.Command of
  //        1001 {QTC_CLIENT_INFO},
  //          1111 {QTC_COLLECTOR_ENTER},
          3101 {QTC_REQUEST_AGENT_LIST},
          3406 {QTC_REQUEST_PI_DRIVELIST} :
          begin
            case PktEncKind of
              ekNone      : sAlgo := 'None';
              ekRc4       : sAlgo := 'RC4';
              ekAes256cbc : sAlgo := 'AES256CBC';
              ekAes192cbc : sAlgo := 'AES192CBC';
              ekAes128cbc : sAlgo := 'AES128CBC';
              else
                sAlgo := 'Unknown';
            end;
            sPath := ExtractFileDrive(GetCurrentPath) + '\QT_ENCTEST\';
            if ForceDirectories(sPath) then
            begin
              sPath := sPath + Format('S-%s-TYP=%d-%s-Cmd=%d.pkt', [FormatDateTime('yyyymmddhhnnss', Now), CLIENT_TYPE, sAlgo, Send.Command]);
              fs := TFileStream.Create(sPath, fmCreate);
              try
                if PktEncKind <> ekNone then
                begin
                  sData := EncodeBase64(pBuf, nLen);
                  fs.Write(@sData[1], Length(sData));
                end else
                  fs.Write(pBuf[0], nLen);
              finally
                fs.Free;
              end;
            end;
          end;
        end;
      {$ENDIF}

        if nLen > 0 then
        begin
        // 헤더 추가
          nTotalLen := LEN_CTX_PACKET_HEADER + nLen;
          SetLength(pSendBuf, SIZE_INTEGER + nTotalLen);
          CopyMemory(@pSendBuf[0], @nTotalLen, SIZE_INTEGER);
          Inc(nTotalLen, SIZE_INTEGER);

          ZeroMemory(@CtxPacketHeader, SizeOf(CtxPacketHeader));
          CopyMemory(@CtxPacketHeader.sSig[0], @CTX_PACKET_SIGNATURE[1], Length(CtxPacketHeader.sSig));
          CtxPacketHeader.dwSize := nLen;
          CtxPacketHeader.wRank := WORD(Send.PacketKind);

          CopyMemory(@pSendBuf[SIZE_INTEGER], @CtxPacketHeader, LEN_CTX_PACKET_HEADER);
          CopyMemory(@pSendBuf[SIZE_INTEGER+LEN_CTX_PACKET_HEADER], @pBuf[0], nLen);

          Client_.Socket.Write(TIdBytes(pSendBuf), nTotalLen);
          Result := true;
        end;
      finally
        Unlock;
        SetLength(pBuf, 0);
      end;
    except
      _ProcessSendFail(Send);
      raise;
    end;
  end;
end;

// 전송중 연결끊김등으로 오유 났을때의 처리대비
// 예) QatorAgent에서 수집데이터 유실방지
procedure TTgClientBase._ProcessSendFail(Send: ISendPacket); begin end;

function TTgClientBase._ProcessOther: Boolean;
begin
  Result := false;

  if (NpIpc_ <> nil) and NpIpc_.IsServer and (NpIpc_.PipeName <> '') then
  begin
    if not TTgNpServer(NpIpc_).Active then
      TTgNpServer(NpIpc_).Listen;

    if TTgNpServer(NpIpc_).Active then
      TTgNpServer(NpIpc_).DoAcceptPipe;
  end;
end;

procedure TTgClientBase.Execute;
var
  Send: ISendPacket;
  bRcv, bSend, bOther: Boolean;
  dwTick: DWORD;
begin
  while not Terminated and not GetWorkStop do
  begin
    try
      Send := DequeueSendPacket;
      if GetConnected then
      begin
        bSend := _ProcessSend(Send);
        bRcv := _ProcessRcv;
        bOther := _ProcessOther;
        if not bRcv and not bSend and not bOther then
        begin
          if NpIpc_ = nil then
          begin
            Sleep(50);
            dwTick := GetTickCount;
            if (dwTick - dwPingTick_) > nPingTerm_ then
            begin
              if W2W_ <> nil then
              begin
                if not IsWindow(hIpcWnd_) then
                begin
                  Disconnect;
                  continue;
                end;
              end else
              if Client_ <> nil then // 기본으로 생성되기 때문에 마지막에 넣음
              begin
                if not Client_.Connected then
                begin
                  Disconnect;
                  continue;
                end;
              end;
  //            _Trace('Try send ping .... nPingTerm_ = %d', [nPingTerm_]);
              _ProcessSend(TTgPacket.Create(0, pkIgnore));
              dwPingTick_ := dwTick;
            end;
          end else Sleep(5);
        end else dwPingTick_ := GetTickCount;
      end else begin
      // todo : 로컬 수집 추가 if Send <> nil then
        Sleep(1000);

        if (NpIpc_ <> nil) and not NpIpc_.IsServer and (NpIpc_.PipeName <> '') then
        begin
          dwTick := GetTickCount;
          if GetTryReconnect and
             ((dwDisconnTick_ = 0) or ((dwTick - dwDisconnTick_) > nReConnTerm_)) then
          begin
            if TTgNpClient(NpIpc_).Connect then
              _ConnectedEvent(nil)
            else
              dwDisconnTick_ := dwTick;
          end;
        end else
        if W2W_ <> nil then
        begin
        // todo : 할게 있을까 23_0112 11:01:54 kku
        end else begin
          dwTick := GetTickCount;
          if GetTryReconnect and
             (sHost_ <> '') and (nPort_ <> 0) and
             ((dwDisconnTick_ = 0) or ((dwTick - dwDisconnTick_) > nReConnTerm_)) then
          begin
            if not Connect(sHost_, nPort_) then
            begin
  //            _Trace('재접속 주기 %d초', [(dwTick - dwDisconnTick_) div 1000]);
              dwDisconnTick_ := dwTick;
            end;
          end;
        end;
      end;
    except
      On e: EIdSocketError do
        Case e.LastError of
          10053,           // 접속 비정상 종료
          10054 : Disconnect;
          else continue;
        End;

      On e: EIdConnClosedGracefully do
      begin
        Disconnect;
      end else continue;
    end;
  end;
end;

end.
