{
  $Project$
  $Workfile$
  $Revision$
  $DateUTC$
  $Id$

  This file is part of the Indy (Internet Direct) project, and is offered
  under the dual-licensing agreement described on the Indy website.
  (http://www.indyproject.org/)

  Copyright:
   (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
}
{
  $Log$
}
{
  Rev 1.1    12/7/2002 06:43:48 PM  JPMugaas
  These should now compile except for Socks server.  IPVersion has to be a
  property someplace for that.


  Rev 1.0    11/13/2002 08:04:00 AM  JPMugaas
}

unit IdTunnelSlave;

interface

{$i IdCompilerDefines.inc}

uses
  SysUtils, Classes, SyncObjs,
  IdTunnelCommon, IdTCPServer, IdTCPClient,
  IdGlobal, IdStack, IdResourceStrings,
  IdThread, IdComponent, IdTCPConnection;

type
  TSlaveThread = class;
  TIdTunnelSlave = class;

  TTunnelEvent   = procedure(Thread: TSlaveThread) of object;

  ///////////////////////////////////////////////////////////////////////////////
  // Slave Tunnel classes
  //
  // Client data structure
  TClientData = class
  public
    Id: Integer;
    TimeOfConnection: TDateTime;
    DisconnectedOnRequest: Boolean;
    SelfDisconnected: Boolean;
    ClientAuthorised: Boolean;
    Locker: TCriticalSection;
    Port: Word;
    IpAddr: TIdInAddr;
    constructor Create;
    destructor Destroy; override;
  end;

  // Slave thread - tunnel thread to communicate with Master
  TSlaveThread = class(TIdThread)
  private
    FLock: TCriticalSection;
    FExecuted: Boolean;
    FConnection: TIdTCPClient;
  protected
    procedure SetExecuted(Value: Boolean);
    function  GetExecuted: Boolean;
    procedure AfterRun; override;
    procedure BeforeRun; override;
  public
    SlaveParent: TIdTunnelSlave;
    Receiver: TReceiver;
    property Executed: Boolean read GetExecuted write SetExecuted;
    property Connection: TIdTCPClient read fConnection;
    constructor Create(Slave: TIdTunnelSlave); reintroduce;
    destructor Destroy; override;
    procedure Execute; override;
    procedure Run; override;
  end;

//  TTunnelEvent   = procedure(Thread: TSlaveThread) of object;

  TIdTunnelSlave = class(TIdTCPServer)
  private
    fiMasterPort: Integer; // Port on which Master Tunnel accepts connections
    fsMasterHost: String; // Address of the Master Tunnel
    SClient: TIdTCPClient; // Client which talks to the Master Tunnel
//    fOnExecute, fOnConnect,
    fOnDisconnect: TIdServerThreadEvent;
    fOnStatus: TIdStatusEvent;
    fOnBeforeTunnelConnect: TSendTrnEventC;
    fOnTransformRead: TTunnelEventC;
    fOnInterpretMsg: TSendMsgEventC;
    fOnTransformSend: TSendTrnEventC;
    fOnTunnelDisconnect: TTunnelEvent;

    Sender: TSender; // Communication class
    OnlyOneThread: TCriticalSection; // Some locking code
    SendThroughTunnelLock: TCriticalSection; // Some locking code
    GetClientThreadLock: TCriticalSection; // Some locking code
//    LockClientNumber: TCriticalSection;
    StatisticsLocker: TCriticalSection;
    ManualDisconnected: Boolean;  // We trigered the disconnection
    StopTransmiting: Boolean;
    fbActive: Boolean;
    fbSocketize: Boolean;
    SlaveThread: TSlaveThread; // Thread which receives data from the Master
    fLogger: TLogger;

    // Statistics counters
    flConnectedClients,        // Number of connected clients
    fNumberOfConnectionsValue,
    fNumberOfPacketsValue,
    fCompressionRatioValue,
    fCompressedBytes,
    fBytesRead,
    fBytesWrite: Integer;

    SlaveThreadTerminated: Boolean;

    procedure SendMsg(var Header: TIdHeader; s: String);
    procedure ClientOperation(Operation: Integer; UserId: Integer; s: String);
    procedure DisconectAllUsers;
    //procedure DoStatus(Sender: TComponent; const sMsg: String);
    function GetNumClients: Integer;
    procedure TerminateTunnelThread;
    function GetClientThread(UserID: Integer): TIdPeerThread;
    procedure OnTunnelThreadTerminate(Sender: TObject);
  protected
    fbAcceptConnections: Boolean; // status if we accept new connections
                                  // it is used with tunnels with some athentication
                                  // procedure between slave and master tunnel

    procedure DoConnect(Thread: TIdPeerThread); override;
    procedure DoDisconnect(Thread: TIdPeerThread); override;
    function DoExecute(Thread: TIdPeerThread): boolean; override;
    procedure DoBeforeTunnelConnect(var Header: TIdHeader; var CustomMsg: String); virtual;
    procedure DoTransformRead(Receiver: TReceiver); virtual;
    procedure DoInterpretMsg(var CustomMsg: String); virtual;
    procedure DoTransformSend(var Header: TIdHeader; var CustomMsg: String); virtual;
    procedure DoStatus(Sender: TComponent; const sMsg: String); virtual;
    procedure DoTunnelDisconnect(Thread: TSlaveThread); virtual;
    procedure LogEvent(Msg: String);
    procedure SetActive(pbValue: Boolean); override;
  public
    procedure SetStatistics(Module: Integer; Value: Integer);
    procedure GetStatistics(Module: Integer; var Value: Integer);
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    //
    property Active: Boolean read FbActive write SetActive;
    property Logger: TLogger read fLogger write fLogger;
    property NumClients: Integer read GetNumClients;
  published
    property MasterHost: string read fsMasterHost write fsMasterHost;
    property MasterPort: Integer read fiMasterPort write fiMasterPort;
    property Socks4: Boolean read fbSocketize write fbSocketize default False;
//    property OnConnect: TIdServerThreadEvent read FOnConnect write FOnConnect;
    property OnDisconnect: TIdServerThreadEvent read FOnDisconnect write FOnDisconnect;
//    property OnExecute: TIdServerThreadEvent read FOnExecute write FOnExecute;
    property OnBeforeTunnelConnect: TSendTrnEventC read fOnBeforeTunnelConnect
                                                  write fOnBeforeTunnelConnect;
    property OnTransformRead: TTunnelEventC read fOnTransformRead
                                            write fOnTransformRead;
    property OnInterpretMsg: TSendMsgEventC read fOnInterpretMsg write fOnInterpretMsg;
    property OnTransformSend: TSendTrnEventC read fOnTransformSend write fOnTransformSend;
    property OnStatus: TIdStatusEvent read FOnStatus write FOnStatus;
    property OnTunnelDisconnect: TTunnelEvent read FOnTunnelDisconnect write FOnTunnelDisconnect;
  end;
  //
  // END Slave Tunnel classes
  ///////////////////////////////////////////////////////////////////////////////

implementation

uses
  IdCoreGlobal,
  IdException,
  IdSocks,
  IdThreadSafe;

Var
  GUniqueID: TIdThreadSafeInteger;

function GetNextID: Integer;
begin
  if Assigned(GUniqueID) then begin
    Result := GUniqueID.Increment;
  end
  else
    result := -1;
end;

///////////////////////////////////////////////////////////////////////////////
// Slave Tunnel classes
//
constructor TIdTunnelSlave.Create(AOwner: TComponent);
begin
  inherited;
  fbActive := False;
  flConnectedClients := 0;
  fNumberOfConnectionsValue := 0;
  fNumberOfPacketsValue := 0;
  fCompressionRatioValue := 0;
  fCompressedBytes := 0;
  fBytesRead := 0;
  fBytesWrite := 0;

  fbAcceptConnections := True;
  SlaveThreadTerminated := False;
  OnlyOneThread := TCriticalSection.Create;
  SendThroughTunnelLock := TCriticalSection.Create;
  GetClientThreadLock := TCriticalSection.Create;
//  LockClientNumber := TCriticalSection.Create;
  StatisticsLocker := TCriticalSection.Create;

  Sender := TSender.Create;
  SClient := TIdTCPClient.Create(nil);
// POZOR MOŽNA NAPAKA
//  SClient.OnStatus := self.DoStatus;  ORIGINAL
  SClient.OnStatus := self.OnStatus;

  ManualDisconnected := False;
  StopTransmiting := False;

end;


destructor TIdTunnelSlave.Destroy;
begin

  fbAcceptConnections := False;
  StopTransmiting := True;
  ManualDisconnected := True;

  Active := False;

//  DisconectAllUsers;

  try
    if SClient.Connected then begin
//      DisconnectedByServer := False;
      SClient.Disconnect;
    end;
  except
    ;
  end;

//  if Assigned(SlaveThread) then
//    if not SlaveThread.Executed then
//      SlaveThread.TerminateAndWaitFor;
  if not SlaveThreadTerminated then
    TerminateTunnelThread;

  FreeAndNil(SClient);
  FreeAndNil(Sender);
//  FreeAndNil(LockClientNumber);
  FreeAndNil(OnlyOneThread);
  FreeAndNil(SendThroughTunnelLock);
  FreeAndNil(GetClientThreadLock);
  FreeAndNil(StatisticsLocker);
  Logger := nil;

  inherited Destroy;
end;

procedure TIdTunnelSlave.LogEvent(Msg: String);
begin
  if Assigned(fLogger) then
    fLogger.LogEvent(Msg);
end;

procedure TIdTunnelSlave.DoStatus(Sender: TComponent; const sMsg: String);
begin
  if Assigned(OnStatus) then begin
    OnStatus(self, hsStatusText, sMsg);
  end;
end;

procedure TIdTunnelSlave.SetActive(pbValue: Boolean);
var
  ErrorConnecting: Boolean;
begin
  // Active = False gets called again by inherited destructor
  if OnlyOneThread = nil then begin
    exit;
  end;

  OnlyOneThread.Enter;
  try

    if fbActive = pbValue then begin
      exit;
    end;

  //  if not ((csLoading in ComponentState) or (csDesigning in ComponentState)) then begin
      if pbValue then begin
  //      DisconnectedByServer := False;
        ManualDisconnected := False;
        StopTransmiting := False;
        ErrorConnecting := False;
        SClient.Host := fsMasterHost;
        SClient.Port := fiMasterPort;
        try
          SClient.Connect;
        except
          fbActive := False;
          IndyRaiseOuterException(EIdTunnelConnectToMasterFailed.Create(RSTunnelConnectToMasterFailed));
          //Exit;
        end;
        if not ErrorConnecting then begin
          SlaveThread := TSlaveThread.Create(self);
          SlaveThreadTerminated := False;
          SlaveThread.Start;
          // Maybe we wait here till authentication of Slave happens
          // here can happen the error if the port is already occupied
          // we must handle this
          try
            inherited SetActive(True);
            fbActive := True;
            fbAcceptConnections := True;
          except
            StopTransmiting := False;
            DisconectAllUsers;
            SClient.Disconnect;
            TerminateTunnelThread;
            fbActive := False;
          end;
        end;
      end
      else begin
        fbAcceptConnections := False;
        StopTransmiting := True;
        ManualDisconnected := True;
//        inherited Active := False;       // Cancel accepting new clients
        inherited SetActive(False);

        DisconectAllUsers;               // Disconnect existing ones
        SClient.Disconnect;
        TerminateTunnelThread;

        fbActive := pbValue;
      end;

  //  end;
    //fbActive := pbValue;

  finally
    OnlyOneThread.Leave;
  end;


end;

function TIdTunnelSlave.GetNumClients: Integer;
var
  ClientsNo: Integer;
begin
  GetStatistics(NumberOfClientsType, ClientsNo);
  Result := ClientsNo;
end;



procedure TIdTunnelSlave.SetStatistics(Module: Integer; Value: Integer);
var
  packets: Real;
  ratio: Real;
begin
  StatisticsLocker.Enter;
  try
    case Module of
      NumberOfClientsType: begin
        if TIdStatisticsOperation(Value) = soIncrease then begin
          Inc(flConnectedClients);
          Inc(fNumberOfConnectionsValue);
        end
        else begin
          Dec(flConnectedClients);
        end;
      end;

      NumberOfConnectionsType: begin
          Inc(fNumberOfConnectionsValue);
      end;

      NumberOfPacketsType: begin
          Inc(fNumberOfPacketsValue);
      end;

      CompressionRatioType: begin
          ratio := fCompressionRatioValue;
          packets := fNumberOfPacketsValue;
          ratio := (ratio/100.0 * (packets - 1.0) + Value/100.0) / packets;
          fCompressionRatioValue := Trunc(ratio * 100);
      end;

      CompressedBytesType: begin
          fCompressedBytes := fCompressedBytes + Value;
      end;

      BytesReadType: begin
          fBytesRead := fBytesRead + Value;
      end;

      BytesWriteType:  begin
          fBytesWrite := fBytesWrite + Value;
      end;
    end;
  finally
    StatisticsLocker.Leave;
  end;
end;


procedure TIdTunnelSlave.GetStatistics(Module: Integer; var Value: Integer);
begin
  StatisticsLocker.Enter;
  try
    case Module of

      NumberOfClientsType: begin
         Value := flConnectedClients;
      end;

      NumberOfConnectionsType: begin
         Value := fNumberOfConnectionsValue;
      end;

      NumberOfPacketsType: begin
         Value := fNumberOfPacketsValue;
      end;

      CompressionRatioType: begin
         if fCompressedBytes > 0 then begin
           Value := Trunc((fBytesRead * 1.0) / (fCompressedBytes * 1.0) * 100.0)
         end
         else begin
           Value := 0;
         end;
      end;

      CompressedBytesType: begin
         Value := fCompressedBytes;
      end;

      BytesReadType: begin
         Value := fBytesRead;
      end;

      BytesWriteType: begin
         Value := fBytesWrite;
      end;

    end;
  finally
    StatisticsLocker.Leave;
  end;
end;


////////////////////////////////////////////////////////////////
//
//  CLIENT SERVICES
//
////////////////////////////////////////////////////////////////
procedure TIdTunnelSlave.DoConnect(Thread: TIdPeerThread);
const
  MAXLINE=255;
var
  SID: Integer;
  s: String;
  req: TIdSocksRequest;
  res: TIdSocksResponse;
  numread: Integer;
  Header: TIdHeader;
begin

  if not fbAcceptConnections then begin
    Thread.Connection.Disconnect;
    // don't allow to enter in OnExecute    {Do not Localize}
    raise EIdTunnelDontAllowConnections.Create (RSTunnelDontAllowConnections);
  end;

  SetStatistics(NumberOfClientsType, Integer(soIncrease));

  Thread.Data := TClientData.Create;

  // Socket version begin
  if fbSocketize then begin
    try
      Thread.Connection.IOHandler.ReadBuffer(req, 8);
    except
      try
        Thread.Connection.Disconnect;
      except
        ;
      end;
      Thread.Terminate;
      Exit;
    end;

    numread := 0;
    repeat
      s := Thread.Connection.ReadString(1);
      req.UserName[numread+1] := s[1];
      Inc(numread);
    until ((numread >= MAXLINE) or (s[1] = #0));
    SetLength(req.UserName, numread);

    s := GStack.TInAddrToString(req.IpAddr);

    res.Version := 0;
    res.OpCode := 90;
    res.Port := req.Port;
    res.IpAddr := req.IpAddr;
    SetString(s, PChar(@res), SizeOf(res));
    Thread.Connection.Write(s);
  end;

  with TClientData(Thread.Data) do begin
    // Id := Thread.Handle;
    SID := Id;
    TimeOfConnection := Now;
    DisconnectedOnRequest := False;
    if fbSocketize then begin
      Port := GStack.WSNToHs(req.Port);
      IpAddr := req.IpAddr;
    end
    else begin
      Port := self.DefaultPort;
      IpAddr.S_addr := 0;
    end;
    Header.Port := Port;
    Header.IpAddr := IpAddr;
  end;

  Header.MsgType := tmConnect;
  Header.UserId := SID;
  SendMsg(Header, RSTunnelConnectMsg);

end;

procedure TIdTunnelSlave.DoDisconnect(Thread: TIdPeerThread);
var
  Header: TIdHeader;
begin

  try
    with TClientData(Thread.Data) do begin
      if DisconnectedOnRequest = False then begin
        Header.MsgType := tmDisconnect;
        Header.UserId := Id;
        SendMsg(Header, RSTunnelDisconnectMsg);
      end;
    end;

    SetStatistics(NumberOfClientsType, Integer(soDecrease));
  except
    ;
  end;

end;

// Thread to communicate with the user
// reads the requests and transmits them through the tunnel
function TIdTunnelSlave.DoExecute(Thread: TIdPeerThread): boolean;
var
  user: TClientData;
  s: String;
  Header: TIdHeader;
begin
  result := true;

  if Thread.Connection.IOHandler.Readable(IdTimeoutInfinite) then  begin
    s := Thread.Connection.CurrentReadBuffer;
    try
      user := TClientData(Thread.Data);
      Header.MsgType := tmData;
      Header.UserId := user.Id;
      SendMsg(Header, s);
    except
      Thread.Connection.Disconnect;
      raise;
    end;
  end;
end;

procedure TIdTunnelSlave.SendMsg(var Header: TIdHeader; s: String);
var
  tmpString: String;
begin

  SendThroughTunnelLock.Enter;
  try
    try

      if not StopTransmiting then begin
        if Length(s) > 0 then begin
          try
            // Custom data transformation before send
            tmpString := s;
            try
              DoTransformSend(Header, tmpString);
            except
              on E: Exception do begin
                raise;
              end;
            end;
            if Header.MsgType = 0 then begin // error ocured in transformation
              raise EIdTunnelTransformErrorBeforeSend.Create(RSTunnelTransformErrorBS);
            end;

            try
            Sender.PrepareMsg(Header, PChar(tmpString), Length(tmpString));
            except
              raise;
            end;

            try
              SClient.Write(Sender.Msg);
            except
              StopTransmiting := True;
              raise;
            end;
          except
            ;
            raise;
          end;
        end
      end;

    except
      SClient.Disconnect;
    end;

  finally
    SendThroughTunnelLock.Leave;
  end;

end;

procedure TIdTunnelSlave.DoBeforeTunnelConnect(var Header: TIdHeader; var CustomMsg: String);
begin

  if Assigned(fOnBeforeTunnelConnect) then
    fOnBeforeTunnelConnect(Header, CustomMsg);

end;

procedure TIdTunnelSlave.DoTransformRead(Receiver: TReceiver);
begin

  if Assigned(fOnTransformRead) then
    fOnTransformRead(Receiver);

end;

procedure TIdTunnelSlave.DoInterpretMsg(var CustomMsg: String);
begin

  if Assigned(fOnInterpretMsg) then
    fOnInterpretMsg(CustomMsg);

end;

procedure TIdTunnelSlave.DoTransformSend(var Header: TIdHeader; var CustomMsg: String);
begin

  if Assigned(fOnTransformSend) then
    fOnTransformSend(Header, CustomMsg);

end;

procedure TIdTunnelSlave.DoTunnelDisconnect(Thread: TSlaveThread);
begin

  try
    StopTransmiting := True;
    if not ManualDisconnected then begin
      if Active then begin
        Active := False;
      end;
    end;
  except
    ;
  end;

  If Assigned(OnTunnelDisconnect) then
    OnTunnelDisconnect(Thread);

end;

procedure TIdTunnelSlave.OnTunnelThreadTerminate(Sender:TObject);
begin
  // Just set the flag
  SlaveThreadTerminated := True;
end;


function TIdTunnelSlave.GetClientThread(UserID: Integer): TIdPeerThread;
var
  user: TClientData;
  Thread: TIdPeerThread;
  i: integer;
begin

//  GetClientThreadLock.Enter;
  Result := nil;
  with ThreadMgr.ActiveThreads.LockList do
  try
    try
      for i := 0 to Count-1 do begin
        try
          if Assigned(Items[i]) then begin
            Thread := TIdPeerThread(Items[i]);
            if Assigned(Thread.Data) then begin
              user := TClientData(Thread.Data);
              if user.Id = UserID then begin
                Result := Thread;
                break;
              end;
            end;
          end;
        except
          Result := nil;
        end;
      end;
    except
      Result := nil;
    end;
  finally
    ThreadMgr.ActiveThreads.UnlockList;
//    GetClientThreadLock.Leave;
  end;
end;


procedure TIdTunnelSlave.TerminateTunnelThread;
begin

  OnlyOneThread.Enter;
  try
    if Assigned(SlaveThread) then begin
      if not IsCurrentThread(SlaveThread) then begin
        SlaveThread.TerminateAndWaitFor;
        SlaveThread.Free;
        SlaveThread := nil;
      end else begin
        SlaveThread.FreeOnTerminate := True;
      end;
    end;
  finally
    OnlyOneThread.Leave;
  end;
end;



procedure TIdTunnelSlave.ClientOperation(Operation: Integer; UserId: Integer; s: String);
var
  Thread: TIdPeerThread;
  user: TClientData;
begin
  if StopTransmiting then begin
    Exit;
  end;
  Thread := GetClientThread(UserID);
  if Assigned(Thread) then begin
    try
      case Operation of
        1:
        begin
          try
            if Thread.Connection.Connected then begin
              try
                Thread.Connection.Write(s);
              except
              end;
            end;
          except
            try
              Thread.Connection.Disconnect;
            except
            end;
          end;
        end;
        2:
        begin
          user := TClientData(Thread.Data);
          user.DisconnectedOnRequest := True;
          Thread.Connection.Disconnect;
        end;
      end;
    except
    end;
  end;
end;

procedure TIdTunnelSlave.DisconectAllUsers;
begin
  TerminateAllThreads;
end;
//
// END Slave Tunnel classes
///////////////////////////////////////////////////////////////////////////////

constructor TClientData.Create;
begin
  inherited Create;
  id := GetNextID;
  Locker := TCriticalSection.Create;
  SelfDisconnected := False;
end;

destructor TClientData.Destroy;
begin
  Locker.Free;
  inherited Destroy;
end;

constructor TSlaveThread.Create(Slave: TIdTunnelSlave);
begin
  SlaveParent := Slave;
//  FreeOnTerminate := True;
  FreeOnTerminate := False;
  FExecuted := False;
  FConnection := Slave.SClient;
  OnTerminate := Slave.OnTunnelThreadTerminate;
//  InitializeCriticalSection(FLock);
  FLock := TCriticalSection.Create;
  Receiver := TReceiver.Create;
  inherited Create(True);
  StopMode := smTerminate;
end;

destructor TSlaveThread.Destroy;
begin
//  Executed := True;
  Connection.Disconnect;
  Receiver.Free;
//  DeleteCriticalSection(FLock);
  FLock.Destroy;
  inherited Destroy;
end;


procedure TSlaveThread.SetExecuted(Value: Boolean);
begin
//  Lock;
  FLock.Enter;
  try
    FExecuted := Value;
  finally
//    Unlock;
    FLock.Leave;
  end;
end;

function TSlaveThread.GetExecuted: Boolean;
begin
//  Lock;
  FLock.Enter;
  try
    Result := FExecuted;
  finally
//    Unlock;
    FLock.Leave;
  end;
end;

procedure TSlaveThread.Execute;
begin
  inherited;
  Executed := True;
end;

procedure TSlaveThread.Run;
var
  Header: TIdHeader;
  s: String;
  CustomMsg: String;
begin
  try
    if Connection.IOHandler.Readable(IdTimeoutInfinite) then begin
//    if Connection.Binding.Readable(IdTimeoutDefault) then begin
      Receiver.Data := Connection.CurrentReadBuffer;

      // increase the packets counter
      SlaveParent.SetStatistics(NumberOfPacketsType, 0);

      while (Receiver.TypeDetected) and (not Terminated) do begin
        if Receiver.NewMessage then begin
          if Receiver.CRCFailed then begin
            raise EIdTunnelCRCFailed.Create(RSTunnelCRCFailed);
          end;

          try
          // Custom data transformation
            SlaveParent.DoTransformRead(Receiver);
          except
            IndyRaiseOuterException(EIdTunnelTransformError.Create(RSTunnelTransformError));
          end;

          // Action
          case Receiver.Header.MsgType of
            0:  // transformation of data failed, disconnect the tunnel
              begin
                SlaveParent.ManualDisconnected := False;
                raise EIdTunnelMessageTypeRecognitionError.Create(RSTunnelMessageTypeError);
              end; // Failure END


            1:  // Data
              begin
                try
                  SetString(s, Receiver.Msg, Receiver.MsgLen);
                  SlaveParent.ClientOperation(1, Receiver.Header.UserId, s);
                except
                  IndyRaiseOuterException(EIdTunnelMessageHandlingFailed.Create(RSTunnelMessageHandlingError));
                end;
              end; // Data END

            2:  // Disconnect
              begin
                try
                  SlaveParent.ClientOperation(2, Receiver.Header.UserId, '');    {Do not Localize}
                except
                  IndyRaiseOuterException(EIdTunnelMessageHandlingFailed.Create(RSTunnelMessageHandlingError));
                end;
              end;

            99:  // Session
              begin
                // Custom data interpretation
                CustomMsg := '';    {Do not Localize}
                SetString(CustomMsg, Receiver.Msg, Receiver.MsgLen);
                try
                  try
                    SlaveParent.DoInterpretMsg(CustomMsg);
                  except
                    IndyRaiseOuterException(EIdTunnelInterpretationOfMessageFailed.Create(RSTunnelMessageInterpretError));
                  end;
                  if Length(CustomMsg) > 0 then begin
                    Header.MsgType := 99;
                    Header.UserId := 0;
                    SlaveParent.SendMsg(Header, CustomMsg);
                  end;
                except
                  SlaveParent.ManualDisconnected := False;
                  IndyRaiseOuterException(EIdTunnelCustomMessageInterpretationFailure.Create(RSTunnelMessageCustomInterpretError));
                end;

              end;

          end; // case

          // Shift of data
          Receiver.ShiftData;

        end
        else
          break;  // break the loop

      end; // end while
    end; // if readable
  except
    on E: EIdSocketError do begin
      case E.LastError of
        10054: Connection.Disconnect;
      else
        begin
          Terminate;
        end;
      end;
    end;
    on EIdClosedSocket do ;
  else
    raise;
  end;
  if not Connection.Connected then
    Terminate;
end;

procedure TSlaveThread.AfterRun;
begin
  SlaveParent.DoTunnelDisconnect(self);
end;

procedure TSlaveThread.BeforeRun;
var
  Header: TIdHeader;
  tmpString: String;
begin
  tmpString := '';    {Do not Localize}
  try
    SlaveParent.DoBeforeTunnelConnect(Header, tmpString);
  except
    ;
  end;
  if Length(tmpString) > 0 then begin
    Header.MsgType := 99;
    Header.UserId := 0;
    SlaveParent.SendMsg(Header, tmpString);
  end;

end;

initialization
  GUniqueID := TIdThreadSafeInteger.Create;
finalization
  FreeAndNil(GUniqueID);
end.
