{
  $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.28    12/2/2004 4:23:54 PM  JPMugaas
  Adjusted for changes in Core.

  Rev 1.27    11/15/04 11:32:22 AM  RLebeau
  Changed OutboundConnect() to assign the TIdTcpClient.ConnectTimeout property
  instead of the IOHandler.ConnectTimeout property.

  Rev 1.26    10/6/2004 10:22:12 PM  BGooijen
  Removed PEVerify errors in this unit

  Rev 1.25    8/2/04 5:55:00 PM  RLebeau
  Updated TIdMappedPortContext.OutboundConnect() with ConnectTimeout property
  change.

  Rev 1.24    3/1/04 7:17:02 PM  RLebeau
  Minor correction to previous change

  Rev 1.23    3/1/04 7:14:34 PM  RLebeau
  Updated TIdMappedPortContext.OutboundConnect() to call CreateIOHandler()
  before attempting to use the outbound client's IOHandler property.

  Rev 1.22    2004.02.03 5:43:58 PM  czhower
  Name changes

  Rev 1.21    2/1/2004 4:23:32 AM  JPMugaas
  Should now compile in DotNET.

  Rev 1.20    1/21/2004 3:11:30 PM  JPMugaas
  InitComponent

  Rev 1.19    12/14/03 7:16:20 PM  RLebeau
  Typo fixes when accessing OutboudClient.IOHandler

  Rev 1.18    2003.11.29 10:19:04 AM  czhower
  Updated for core change to InputBuffer.

  Rev 1.17    2003.10.21 9:13:10 PM  czhower
  Now compiles.

  Rev 1.16    10/19/2003 5:25:10 PM  DSiders
  Added localization comments.

  Rev 1.15    2003.10.18 9:42:10 PM  czhower
  Boatload of bug fixes to command handlers.

  Rev 1.14    2003.10.12 4:04:00 PM  czhower
  compile todos

  Rev 1.13    9/19/2003 03:30:06 PM  JPMugaas
  Now should compile again.

  Rev 1.12    04.06.2003 14:09:28  ARybin
  updated for new IoHandler behavior

  Rev 1.11    3/6/2003 5:08:46 PM  SGrobety
  Updated the read buffer methodes to fit the new core (InputBuffer ->
  InputBufferAsString + call to CheckForDataOnSource)

  Rev 1.10    28.05.2003 17:35:34  ARybin
  right bug fix

  Rev 1.9    28.05.2003 16:55:30  ARybin
  bug fix

  Rev 1.8    5/26/2003 12:23:46 PM  JPMugaas

  Rev 1.7    4/3/2003 7:26:10 PM  BGooijen
  Re-enabled .ConnectTimeout and .Connect

  Rev 1.6    3/21/2003 11:45:36 AM  JPMugaas
  Added OnBeforeConnect method so the TIdMappedPort component is more flexible.

  Rev 1.5    2/24/2003 09:14:38 PM  JPMugaas

  Rev 1.4    1/20/2003 1:15:32 PM  BGooijen
  Changed to TIdTCPServer / TIdCmdTCPServer classes

  Rev 1.3    1/17/2003 06:45:12 PM  JPMugaas
  Now compiles with new framework.

  Rev 1.2    1-8-2003 22:20:40  BGooijen
  these compile (TIdContext)

  Rev 1.1    12/7/2002 06:43:10 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 07:56:42 AM  JPMugaas

  2001-12-xx - Andrew P.Rybin
    -new architecture

  2002-02-02 - Andrew P.Rybin
    -DoDisconnect fix
}

unit IdMappedPortTCP;

interface

{$i IdCompilerDefines.inc}

uses
  Classes,
  IdAssignedNumbers,
  IdContext,
  IdCustomTCPServer,
  IdGlobal, IdStack, IdTCPConnection, IdTCPServer, IdYarn, SysUtils;

type
  TIdMappedPortTCP = class;

  TIdMappedPortContext = class(TIdServerContext)
  protected
    FOutboundClient: TIdTCPConnection;//was TIdTCPClient
    FReadList: TIdSocketList;
    FDataAvailList: TIdSocketList;
    FConnectTimeOut: Integer;
    FNetData: TIdBytes;
    FServer : TIdMappedPortTCP;
    //
    procedure CheckForData(DoRead: Boolean); virtual;
    procedure HandleLocalClientData; virtual;
    procedure HandleOutboundClientData; virtual;
    procedure OutboundConnect; virtual;
  public
    constructor Create(
      AConnection: TIdTCPConnection;
      AYarn: TIdYarn;
      AList: TIdContextThreadList = nil
      ); override;
    destructor Destroy; override;
    //
    property  Server : TIdMappedPortTCP Read FServer write FServer;
    property  ConnectTimeOut: Integer read FConnectTimeOut write FConnectTimeOut default IdTimeoutDefault;
    property  NetData: TIdBytes read FNetData write FNetData;
    property  OutboundClient: TIdTCPConnection read FOutboundClient write FOutboundClient;
  end;//TIdMappedPortContext

  TIdMappedPortTCP = class(TIdCustomTCPServer)
  protected
    FMappedHost: string;
    FMappedPort: TIdPort;
    FOnBeforeConnect: TIdServerThreadEvent;

    //AThread.Connection.Server & AThread.OutboundClient
    FOnOutboundConnect: TIdServerThreadEvent;
    FOnOutboundData: TIdServerThreadEvent;
    FOnOutboundDisConnect: TIdServerThreadEvent;
    //
    procedure ContextCreated(AContext:TIdContext); override;
    procedure DoBeforeConnect(AContext: TIdContext); virtual;
    procedure DoConnect(AContext: TIdContext); override;
    function  DoExecute(AContext: TIdContext): boolean; override;
    procedure DoDisconnect(AContext: TIdContext); override; //DoLocalClientDisconnect
    procedure DoLocalClientConnect(AContext: TIdContext); virtual;
    procedure DoLocalClientData(AContext: TIdContext); virtual;//APR: bServer

    procedure DoOutboundClientConnect(AContext: TIdContext); virtual;
    procedure DoOutboundClientData(AContext: TIdContext); virtual;
    procedure DoOutboundDisconnect(AContext: TIdContext); virtual;
    function  GetOnConnect: TIdServerThreadEvent;
    function  GetOnExecute: TIdServerThreadEvent;
    procedure SetOnConnect(const Value: TIdServerThreadEvent);
    procedure SetOnExecute(const Value: TIdServerThreadEvent);
    function  GetOnDisconnect: TIdServerThreadEvent;
    procedure SetOnDisconnect(const Value: TIdServerThreadEvent);
    procedure InitComponent; override;
  published
    property  OnBeforeConnect: TIdServerThreadEvent read FOnBeforeConnect write FOnBeforeConnect;
    property  MappedHost: String read FMappedHost write FMappedHost;
    property  MappedPort: TIdPort read FMappedPort write FMappedPort;
    //
    property  OnConnect: TIdServerThreadEvent read GetOnConnect write SetOnConnect; //OnLocalClientConnect
    property  OnOutboundConnect: TIdServerThreadEvent read FOnOutboundConnect write FOnOutboundConnect;

    property  OnExecute: TIdServerThreadEvent read GetOnExecute write SetOnExecute;//OnLocalClientData
    property  OnOutboundData: TIdServerThreadEvent read FOnOutboundData write FOnOutboundData;

    property  OnDisconnect: TIdServerThreadEvent read GetOnDisconnect write SetOnDisconnect;//OnLocalClientDisconnect
    property  OnOutboundDisconnect: TIdServerThreadEvent read FOnOutboundDisconnect write FOnOutboundDisconnect;
  End;//TIdMappedPortTCP

Implementation

uses
  IdException,
  IdIOHandler, IdIOHandlerSocket, IdResourceStrings,IdStackConsts, IdTCPClient;

procedure TIdMappedPortTCP.InitComponent;
begin
  inherited InitComponent;
  FContextClass := TIdMappedPortContext;
end;

procedure TIdMappedPortTCP.ContextCreated(AContext: TIdContext);
begin
  TIdMappedPortContext(AContext).Server := Self;
end;

procedure TIdMappedPortTCP.DoBeforeConnect(AContext: TIdContext);
begin
  if Assigned(FOnBeforeConnect) then begin
    FOnBeforeConnect(AContext);
  end;
end;

procedure TIdMappedPortTCP.DoLocalClientConnect(AContext: TIdContext);
begin
  if Assigned(FOnConnect) then begin
    FOnConnect(AContext);
  end;
end;

procedure TIdMappedPortTCP.DoOutboundClientConnect(AContext: TIdContext);
begin
  if Assigned(FOnOutboundConnect) then begin
    FOnOutboundConnect(AContext);
  end;
end;

procedure TIdMappedPortTCP.DoLocalClientData(AContext: TIdContext);
begin
  if Assigned(FOnExecute) then begin
    FOnExecute(AContext);
  end;
end;

procedure TIdMappedPortTCP.DoOutboundClientData(AContext: TIdContext);
begin
  if Assigned(FOnOutboundData) then begin
    FOnOutboundData(AContext);
  end;
end;

procedure TIdMappedPortTCP.DoDisconnect(AContext: TIdContext);
begin
  inherited DoDisconnect(AContext);
  //check for loop
  if Assigned(TIdMappedPortContext(AContext).FOutboundClient) and
    TIdMappedPortContext(AContext).FOutboundClient.Connected then
  begin
    TIdMappedPortContext(AContext).FOutboundClient.Disconnect;
  end;
end;

procedure TIdMappedPortTCP.DoOutboundDisconnect(AContext: TIdContext);
begin
  if Assigned(FOnOutboundDisconnect) then begin
    FOnOutboundDisconnect(AContext);
  end;
  AContext.Connection.Disconnect; //disconnect local
end;

procedure TIdMappedPortTCP.DoConnect(AContext: TIdContext);
var
  LContext: TIdMappedPortContext;
begin
  DoBeforeConnect(AContext);

  LContext := TIdMappedPortContext(AContext);

  //WARNING: Check TIdTCPServer.DoConnect and synchronize code. Don't call inherited!=> OnConnect in OutboundConnect    {Do not Localize}
  LContext.OutboundConnect;

  //cache
  LContext.FReadList.Clear;
  LContext.FReadList.Add(AContext.Connection.Socket.Binding.Handle);
  LContext.FReadList.Add(LContext.FOutboundClient.Socket.Binding.Handle);
end;

function TIdMappedPortTCP.DoExecute(AContext: TIdContext): Boolean;
var
  LContext: TIdMappedPortContext;
begin
  LContext := TIdMappedPortContext(AContext);
  try
    LContext.CheckForData(True);
  finally
    if not LContext.FOutboundClient.Connected then begin
      Result := False;
      DoOutboundDisconnect(AContext); //&Connection.Disconnect
    end else begin;
      Result := AContext.Connection.Connected;
    end;
  end;
end;

function TIdMappedPortTCP.GetOnConnect: TIdServerThreadEvent;
begin
  Result := FOnConnect;
end;

function TIdMappedPortTCP.GetOnExecute: TIdServerThreadEvent;
begin
  Result := FOnExecute;
end;

function TIdMappedPortTCP.GetOnDisconnect: TIdServerThreadEvent;
begin
  Result := FOnDisconnect;
end;

procedure TIdMappedPortTCP.SetOnConnect(const Value: TIdServerThreadEvent);
begin
  FOnConnect := Value;
end;

procedure TIdMappedPortTCP.SetOnExecute(const Value: TIdServerThreadEvent);
begin
  FOnExecute := Value;
end;

procedure TIdMappedPortTCP.SetOnDisconnect(const Value: TIdServerThreadEvent);
begin
  FOnDisconnect := Value;
end;


{ TIdMappedPortContext }

constructor TIdMappedPortContext.Create(
  AConnection: TIdTCPConnection;
  AYarn: TIdYarn;
  AList: TIdContextThreadList = nil
  );
begin
  inherited Create(AConnection, AYarn, AList);
  FReadList := TIdSocketList.CreateSocketList;
  FDataAvailList := TIdSocketList.CreateSocketList;
  FConnectTimeOut := IdTimeoutDefault;
end;

destructor TIdMappedPortContext.Destroy;
begin
  FreeAndNil(FOutboundClient);
  FreeAndNIL(FReadList);
  FreeAndNIL(FDataAvailList);
  inherited Destroy;
end;

procedure TIdMappedPortContext.CheckForData(DoRead: Boolean);
begin
  if DoRead and Connection.IOHandler.InputBufferIsEmpty and FOutboundClient.IOHandler.InputBufferIsEmpty then
  begin
    if FReadList.SelectReadList(FDataAvailList, IdTimeoutInfinite) then
    begin
      //1.LConnectionHandle
      if FDataAvailList.ContainsSocket(Connection.Socket.Binding.Handle) then
      begin
        // TODO: WSAECONNRESET (Exception [EIdSocketError] Socket Error # 10054 Connection reset by peer)
        Connection.IOHandler.CheckForDataOnSource(0);
      end;
      //2.LOutBoundHandle
      if FDataAvailList.ContainsSocket(FOutboundClient.Socket.Binding.Handle) then
      begin
        FOutboundClient.IOHandler.CheckForDataOnSource(0);
      end;
    end;        
  end;
  if not Connection.IOHandler.InputBufferIsEmpty then
  begin
    HandleLocalClientData;
  end;
  if not FOutboundClient.IOHandler.InputBufferIsEmpty then
  begin
    HandleOutboundClientData;
  end;
  Connection.IOHandler.CheckForDisconnect;
  FOutboundClient.IOHandler.CheckForDisconnect;
end;

procedure TIdMappedPortContext.HandleLocalClientData;
begin
  SetLength(FNetData, 0);
  Connection.IOHandler.InputBuffer.ExtractToBytes(FNetData);
  Server.DoLocalClientData(Self);
  FOutboundClient.IOHandler.Write(FNetData);
end;

procedure TIdMappedPortContext.HandleOutboundClientData;
begin
  SetLength(FNetData, 0);
  FOutboundClient.IOHandler.InputBuffer.ExtractToBytes(FNetData);
  Server.DoOutboundClientData(Self);
  Connection.IOHandler.Write(FNetData);
end;

procedure TIdMappedPortContext.OutboundConnect;
var
  LServer: TIdMappedPortTCP;
  LClient: TIdTCPClient;
begin
  FOutboundClient := TIdTCPClient.Create(nil);
  LServer := TIdMappedPortTCP(Server);
  try
    LClient := TIdTCPClient(FOutboundClient);

    LClient.Port := LServer.MappedPort;
    LClient.Host := LServer.MappedHost;

    LServer.DoLocalClientConnect(Self);

    LClient.ConnectTimeout := FConnectTimeOut;
    LClient.Connect;

    LServer.DoOutboundClientConnect(Self);

    //APR: buffer can contain data from prev (users) read op.
    CheckForData(False);
  except
    on E: Exception do
    begin
      DoException(E);// DONE: Handle connect failures
      Connection.Disconnect; //req IdTcpServer with "Stop this thread if we were disconnected"
      raise;
    end;
  end;
end;

end.
