lazarus-ccr/components/flashfiler/sourcelaz/ffllprot.pas
2016-12-07 13:31:59 +00:00

2994 lines
99 KiB
ObjectPascal

{*********************************************************}
{* FlashFiler: Communications protocol class *}
{*********************************************************}
(* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is TurboPower FlashFiler
*
* The Initial Developer of the Original Code is
* TurboPower Software
*
* Portions created by the Initial Developer are Copyright (C) 1996-2002
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* ***** END LICENSE BLOCK ***** *)
{$I ffdefine.inc}
{ Enable the following line to activate Keep Alive logging. }
{.$DEFINE KALog}
unit ffllprot;
interface
uses
{$ifdef fpc}LCLIntf{$endif}, //soner LCLIntf for functions AllocateHWnd and DeallocateHWnd and it must be firt because it changes tmsg and others from windows
Windows,
Messages,
SysUtils,
Classes,
ExtCtrls,
Forms,
ffconst,
ffllbase,
ffllexcp,
fflllog,
ffllwsct,
ffnetmsg,
ffsrmgr,
ffllwsck;
type
TffProtocolType = ( {Protocol types..}
ptSingleUser, {..single user}
ptTCPIP, {..TCP/IP}
ptIPXSPX, {..IPX/SPX}
ptRegistry); {..value from registry}
{===Constants relating to sending messages and datagrams}
const
ffc_ConnectRetryTimeout : DWORD = 1000; {!!.05}
{ Number of milliseconds before retry of connection request. } {!!.05}
ffc_UnblockWait : DWORD = 25; {!!.06}
{ Number of milliseconds to wait before exiting unblock wait loop. } {!!.06}
ffc_MaxWinsockMsgSize = 24 * 1024;
ffc_MaxSingleUserMsgSize = 64 * 1024;
ffc_MaxDatagramSize = 128;
ffc_CodeLength = 256;
ffc_LastMsgInterval : longint = 30000;
ffc_KeepAliveInterval : longint = 5000;
ffc_KeepAliveRetries : longint = 5;
ffc_TCPInterface : Integer = 0; // NIC to use for TCPIP
ffc_TCPRcvBuf : longint = $8000; // request 32K Rcv Buffer
ffc_TCPSndBuf : longint = $8000; // request 32K Snd Buffer
ffc_SingleUserServerName = 'Local server';
ffc_SendMessageTimeout = 1 * 1000; {1 second} {!!.01}{!!.05}
ffc_SUPErrorTimeout : Integer = 25; {!!.06}
{ # milliseconds to wait if error occurs during SUP send. } {!!.06}
{===Single user messages constants (for dwData)}
const
ffsumCallServer = $4631;
ffsumDataMsg = $4632;
ffsumHangUp = $4633;
ffsumKeepAlive = $4634;
ffm_ServerReply = WM_USER + $0FF9;
{===Datagram types===}
type
PffDatagram = ^TffDatagram;
TffDatagram = array [0..pred(ffc_MaxDatagramSize)] of byte;
PffDatagramArray = ^TffDatagramArray;
TffDatagramArray = array [0..255] of TffDatagram;
{===Code types===}
type
PffNetMsgCode = ^TffNetMsgCode;
TffNetMsgCode = array [0..pred(ffc_CodeLength)] of byte;
{===Event types===}
type
TffReceiveMsgEvent = function (aSender : TObject;
clientID : TffClientID;
replyData : PffByteArray;
replyLen : longInt) : boolean of object;
TffHeardCallEvent = procedure (aSender : TObject;
aConnHandle : longint) of object;
TffReceiveDatagramEvent = procedure (aSender : TObject;
const aName : TffNetName;
aData : PffByteArray;
aDataLen : longint) of object;
TffHangUpEvent = procedure (aSender : TObject;
aClientID : TffClientID) of object;
{===Base Classes===}
type
TffBaseCommsProtocol = class;
TffClientServerType = ( {type defining client or server}
csClient, {..client}
csServer); {..server}
TffConnection = class(TffSelfListItem)
protected {private}
FClientID : TffClientID;
FCode : PffNetMsgCode;
{ The code used for encrypting messages. }
FCodeStart : DWord; {!!.10}
FHangingUp : boolean;
FHangupDone : boolean; {!!.01}
FHangupLock : TffPadlock; {!!.01}
FOwner : TffBaseCommsProtocol;
FRemoteName : PffShStr;
FAliveRetries : integer;
FLastMsgTimer : TffTimer;
FSendConfirm : boolean;
protected
function GetRemoteName : string; {!!.10}
procedure AddToList(List : TFFList); virtual;
procedure RemoveFromList(List : TFFList); virtual;
public
constructor Create(aOwner : TffBaseCommsProtocol;
aRemoteName : TffNetAddress);
destructor Destroy; override;
procedure ConfirmAlive(SendConfirm : boolean);
procedure DepleteLife;
procedure HangupLock; {!!.01}
procedure HangupUnlock; {!!.01}
procedure InitCode(const aStart : longint);
{ Initializes the encryption code used for communicating with the
server. }
function IsAlive : boolean;
function IsVeryAlive : boolean;
function NeedsConfirmSent : boolean;
property ClientID : TffClientID read FClientID write FClientID;
property Code : PffNetMsgCode read FCode;
property CodeStart : DWord read FCodeStart; {!!.10}
property Owner : TffBaseCommsProtocol
read FOwner;
property Handle : longint
read KeyAsInt;
property HangingUp : boolean
read FHangingUp write FHangingUp;
{ Set to True when we are deliberately hanging up the connection.
This variable tells us whether we need to invoke the OnHangUp or
OnConnectionLost event in the parent protocol. }
property HangupDone : boolean {!!.01}
read FHangupDone write FHangupDone; {!!.01}
property RemoteName : string {!!.10}
read GetRemoteName;
end;
{ Defines the common interface for all legacy protocols. This class is
written with the assumption that only one thread will ever be using an
instance of this class at any given time. Therefore no locking/critical
sections are used. }
TffBaseCommsProtocol = class
protected {private}
FConnLock : TffPadlock;
FCSType : TffClientServerType;
FEventLog : TffBaseLog;
FHeardCall : TffHeardCallEvent;
FKeepAliveInterval : longInt;
FKeepAliveRetries : longInt;
FLastMsgInterval : longInt;
FLocalName : PffShStr;
FLogEnabled : boolean;
FMaxNetMsgSize : longint;
FNetName : PffShStr;
FNotifyWindow : HWND;
FOnConnectionLost : TffHangupEvent;
FOnHangup : TffHangUpEvent;
FReceiveDatagram : TffReceiveDatagramEvent;
FReceiveMsg : TffReceiveMsgEvent;
FSearchTimeOut : integer;
FStarted : boolean;
{-If True then the protocol is active. }
FStartedEvent : TffEvent;
cpConnList : TffList;
cpIndexByOSConnector : TffList; { This is an index by socket (TCP/IP or
IPX/SPX) or by window handle (SUP). }
cpIndexByClient : TffList; { This is an index by clientID. }
protected
function GetLocalName : string; {!!.10}
function GetNetName : string; {!!.10}
procedure cpAddConnection(aConnection : TffConnection);
function cpExistsConnection(aConnHandle : longint) : boolean;
function cpFindConnection(const aClientID : TffClientID) : Longint;
function cpGetConnection(const aClientID : TffClientID) : TffConnection;
function cpGetConnectionIDs(const anIndex : longInt) : TffClientID;
procedure cpRemoveConnection(aClientID : TffClientID);
function cpCreateNotifyWindow : boolean; dynamic;
procedure cpDestroyNotifyWindow;
procedure cpDoHangUp(aConn : TffConnection); dynamic;
procedure cpDoHeardCall(aConnHandle : longint); dynamic;
procedure cpDoReceiveDatagram(const aName : TffNetName;
aData : PffByteArray;
aDataLen : longint); dynamic;
function cpDoReceiveMsg(aConn : TffConnection;
msgData : PffByteArray;
msgDataLen : longInt) : boolean; dynamic;
procedure cpPerformShutdown; virtual;
procedure cpPerformStartUp; virtual; abstract;
procedure cpSetNetName(aName : string);
procedure cpCodeMessage(aConn : TffConnection; aData : PffByteArray;
aDataLen : longint); virtual;
procedure cpGotCheckConnection(aConn : TffConnection);
procedure cpTimerTick;
public
constructor Create(const aName : TffNetAddress; aCSType : TffClientServerType); virtual;
destructor Destroy; override;
function Call(const aServerName : TffNetName;
var aClientID : TffClientID;
const timeout : longInt) : TffResult; virtual; abstract;
function ClientIDExists(const aClientID : TffClientID) : boolean;
{ Used by the legacy transport to determine if it has generated a
temporary clientID that conflicts with a real clientID. }
function ConnectionCount : longInt;
{ Returns the number of established connections. }
procedure ConnLock;
procedure ConnUnlock;
{ Use these procedures to prevent a newly-attached client from sending
the protocol a message before the protocol has updated the new
connection's clientID. }
procedure GetServerNames(aList : TStrings; const timeout : longInt); virtual; abstract;
{ Protocol-specific method for retrieving servers accessible via the
protocol. }
function GetCodeStart(const aClientID : TffClientID) : integer;
{ Get the starting encryption code for the specified client. }
class function GetProtocolName : string; virtual;
{ Returns the name of the protocol (e.g., 'TCP/IP'). }
procedure HangUp(aConn : TffConnection); virtual; abstract;
procedure HangUpByClientID(aClientID : TffClientID); virtual;
procedure HangupDone(aClientID : TffClientID); {!!.01}
function HangupIsDone(aClientID : TffClientID) : Boolean; {!!.01}
procedure HangupLock(aClientID : TffClientID); {!!.01}
procedure HangupUnlock(aClientID : TffClientID); {!!.01}
procedure Listen; virtual; abstract;
function SendMsg(aClientID : TffClientID;
aData : PffByteArray;
aDataLen : longint;
aConnLock : Boolean) : TffResult; virtual; abstract; {!!.06}
procedure ReceiveDatagram; virtual; abstract;
procedure SendDatagram(const aName : TffNetName;
aData : PffByteArray;
aDataLen : longint); virtual; abstract;
procedure Shutdown; virtual;
procedure StartUp; virtual;
procedure StopReceiveDatagram; virtual; abstract;
class function Supported : boolean; virtual;
{ Returns True if the protocol is supported on this workstation.
Default implementation always returns True. }
procedure Breathe; virtual;
procedure InitCode(const aClientID : TffClientID;
const aStart : longint);
procedure ResetKeepAliveTimer;
procedure UpdateClientID(const oldClientID, newClientID : TffClientID);
{ After a client has successfully obtained access to the server, the
transport uses this method to replace the client's temporary ID
with the ID returned from the server. }
procedure LogStr(const aMsg : string);
{ Use this method to write an event string to the protocol's event
log. }
procedure LogStrFmt(const aMsg : string; args : array of const);
{ Use this method to write a formatted event string to the protocol's
event log. }
property ConnectionIDs[const anIndex : longInt] : TffClientID
read cpGetConnectionIDs;
{ Use this method to retrieve the connection IDs for the protocol's
connections. }
property CSType : TffClientServerType
read FCSType;
property EventLog : TffBaseLog
read FEventLog write FEventLog;
property IsStarted : boolean
read FStarted;
property KeepAliveInterval : longInt
read FKeepAliveInterval
write FKeepAliveInterval;
property KeepAliveRetries : longInt
read FKeepAliveRetries
write FKeepAliveRetries;
property LastMsgInterval : longInt
read FLastMsgInterval
write FLastMsgInterval;
property LocalName : string {!!.10}
read GetLocalName;
property LogEnabled : boolean
read FLogEnabled
write FLogEnabled;
property MaxNetMsgSize : longint
read FMaxNetMsgSize;
property NetName : string {!!.10}
read GetNetName;
property NotifyWindow : HWND
read FNotifyWindow;
property OnConnectionLost : TffHangUpEvent
read FOnConnectionLost write FOnConnectionLost;
{ This event is called when the other end of the connection unexpectedly
hangs up on this end. }
property OnHangUp : TffHangUpEvent
read FOnHangUp write FOnHangUp;
{ This event is called when the protocol deliberately hangs up the
connection. }
property OnHeardCall : TffHeardCallEvent
read FHeardCall write FHeardCall;
property OnReceiveDatagram: TffReceiveDatagramEvent
read FReceiveDatagram write FReceiveDatagram;
property OnReceiveMsg : TffReceiveMsgEvent
read FReceiveMsg write FReceiveMsg;
property SearchTimeOut : integer
read FSearchTimeOut;
property StartedEvent : TffEvent
read FStartedEvent;
end;
TffCommsProtocolClass = class of TffBaseCommsProtocol;
{===Winsock Classes===}
type
PffwscPacket = ^TffwscPacket;
TffwscPacket = packed record
dwLength : longint;
dwStart : longint;
lpData : PffByteArray;
lpNext : PffwscPacket;
end;
type
TffWinsockConnection = class(TffConnection)
protected {private}
FSocket : TffwsSocket;
FFamily : TffWinsockFamily;
wscNotifyWnd : HWND;
// wscPortal : TffReadWritePortal; {Deleted !!.05}
{!!.05 - Replaced by TffConnection.HangupLock }
{ Controls access to a connection in order that:
1. The connection is not freed while a reply is outgoing.
2. No more than one reply is being sent to the connection at
any one time.
}
wscRcvBuffer : PffByteArray;
wscRcvBufOfs : integer;
// wscSendBuffer : PffByteArray;
protected
wscRcvBuf : longint;
wscSndBuf : longint;
wscPacketHead : PffwscPacket;
wscPacketTail : PffwscPacket;
wscIsSending : Boolean;
procedure AddToList(List : TFFList); override;
procedure RemoveFromList(List : TFFList); override;
public
constructor Create(aOwner : TffBaseCommsProtocol;
aRemoteName : TffNetAddress;
aSocket : TffwsSocket;
aFamily : TffWinsockFamily;
aNotifyWnd : HWND);
destructor Destroy; override;
function Send(aData : PffByteArray;
aDataLen : longint;
aDataStart : longint;
var aBytesSent : longint;
aConnLock : Boolean) : integer; {!!.06}
procedure StartReceive;
property IsSending : Boolean {!!.06}
read wscIsSending write wscIsSending; {!!.06}
property RcvBuffer : PffByteArray
read wscRcvBuffer;
property RcvBufferOffset : integer
read wscRcvBufOfs write wscRcvBufOfs;
property Socket : TffwsSocket
read FSocket;
end;
type
TffWinsockProtocol = class(TffBaseCommsProtocol)
protected {private}
FCollectingServerNames : boolean;
FDatagramPadlock : TffPadlock;
FFamily : TffWinsockFamily;
FServerNames : TStringList;
wspLocalInAddr : TffwsInAddr;
wspLocalIPXNetNum : TffwsIPXNetNum;
wspLocalIPXAddr : TffwsIPXAddr;
wspListening : boolean;
wspListenSocket : TffwsSocket;
wspRcvDatagramSocket : TffwsSocket;
wspRcvDGBuffer : PffByteArray;
wspReceivingDatagram : boolean;
wspWaitingForConnect : boolean;
wspWaitingForSendToUnblock : boolean;
protected
procedure SetFamily(F : TffWinsockFamily);
function cpCreateNotifyWindow : boolean; override;
procedure cpDoReceiveDatagram(const aName : TffNetName;
aData : PffByteArray;
aDataLen : longint); override;
procedure cpPerformStartUp; override;
procedure wspConnectCompleted(aSocket : TffwsSocket);
function wspGetConnForSocket(aSocket : TffwsSocket) : TffWinsockConnection;
procedure wspHangupDetected(aSocket : TffwsSocket);
procedure wspListenCompleted(aSocket : TffwsSocket);
procedure wspProcessCompletedWSACall(WParam, LParam : longint);
procedure wspSendMsgCompleted(aSocket : TffwsSocket);
procedure wspReceiveCompleted(aSocket : TffwsSocket);
procedure wspReceiveDatagramCompleted(aSocket : TffwsSocket);
procedure wspReceiveMsgCompleted(aSocket : TffwsSocket);
procedure wspWaitForConnect(aTimeOut : integer);
function wspWaitForSendToUnblock : Boolean; {!!.06}
procedure wspWSAEventCompleted(var WSMsg : TMessage);
public
constructor Create(const aName : TffNetAddress;
aCSType : TffClientServerType); override;
destructor Destroy; override;
function Call(const aServerName : TffNetName;
var aClientID : TffClientID;
const timeOut : longInt) : TffResult; override;
procedure GetServerNames(aList : TStrings; const timeout : longInt); override;
procedure HangUp(aConn : TffConnection); override;
procedure Listen; override;
function SendMsg(aClientID : TffClientID;
aData : PffByteArray;
aDataLen : longint;
aConnLock : Boolean) : TffResult; override; {!!.06}
procedure ReceiveDatagram; override;
procedure SendDatagram(const aName : TffNetName;
aData : PffByteArray;
aDataLen : longint); override;
procedure StopReceiveDatagram; override;
property Family : TffWinsockFamily
read FFamily write SetFamily;
end;
TffTCPIPProtocol = class(TffWinsockProtocol)
protected
public
constructor Create(const aName : TffNetAddress;
aCSType : TffClientServerType); override;
class function GetProtocolName : string; override;
{ Returns the name of the protocol (e.g., 'TCP/IP'). }
class function Supported : boolean; override;
end;
TffIPXSPXProtocol = class(TffWinsockProtocol)
protected
public
constructor Create(const aName : TffNetAddress;
aCSType : TffClientServerType); override;
class function GetProtocolName : string; override;
{ Returns the name of the protocol (e.g., 'TCP/IP'). }
class function Supported : boolean; override;
end;
TffSingleUserConnection = class(TffConnection)
protected {private}
FPartner : HWND;
FUs : HWND;
sucSendBuffer : PffByteArray;
protected
procedure AddToList(List : TFFList); override;
procedure RemoveFromList(List : TFFList); override;
public
constructor Create(aOwner : TffBaseCommsProtocol;
aRemoteName : TffNetAddress;
aUs : HWND;
aPartner : HWND);
destructor Destroy; override;
procedure Send(aData : PffByteArray;
aDataLen : longint;
aConnLock : Boolean); {!!.06}
property Partner : HWND read FPartner write FPartner;
end;
TffSingleUserProtocol = class(TffBaseCommsProtocol)
protected {private}
supMsgID : TffWord32;
supPostMsgID : TffWord32;
supPartner : HWND;
supReceivingDatagram : boolean;
protected
function cpCreateNotifyWindow : boolean; override;
procedure cpPerformStartUp; override;
procedure supDataMsgReceived(const aClientID : TffClientID;
const aCDS : TCopyDataStruct);
function supGetConnForPartner(aPartner : HWND) : TffSingleUserConnection;
procedure supHangupDetected(const aClientID : TffClientID);
procedure supListenCompleted(aClientID : TffClientID; Wnd : HWND);
procedure supMsgReceived(var SUMsg : TMessage);
function supFindPartner(const aClientID : TffClientID;
const timeout : longInt): HWND;
public
constructor Create(const aName : TffNetAddress; aCSType : TffClientServerType); override;
function Call(const aServerName : TffNetName;
var aClientID : TffClientID;
const timeout : longInt) : TffResult; override;
class function GetProtocolName : string; override;
{ Returns the name of the protocol (e.g., 'TCP/IP'). }
procedure GetServerNames(aList : TStrings; const timeout : longInt); override;
procedure HangUp(aConn : TffConnection); override;
procedure Listen; override;
function SendMsg(aClientID : TffClientID;
aData : PffByteArray;
aDataLen : longint;
aConnLock : Boolean) : TffResult; override; {!!.06}
procedure ReceiveDatagram; override;
procedure SendDatagram(const aName : TffNetName;
aData : PffByteArray;
aDataLen : longint); override;
procedure StopReceiveDatagram; override;
end;
{===Helper routines===}
procedure FFSplitNetAddress(const aAddress : TffNetAddress;
var aLocalName : TffNetName;
var aNetName : TffNetName);
procedure FFMakeNetAddress(var aAddress : TffNetAddress;
const aLocalName : TffNetName;
const aNetName : TffNetName);
{ TCP & UDP - FFSetxxx routines expect port number to be in
host byte order. }
procedure FFSetTCPPort(const aPort : integer);
procedure FFSetUDPPortServer (const aPort : integer);
procedure FFSetUDPPortClient (const aPort : integer);
function FFGetTCPPort : integer;
function FFGetUDPPortServer : integer;
function FFGetUDPPortClient : integer;
{ IPX/SPX - FFSetxxx routines expect port number to be in
host byte order. }
procedure FFSetIPXSocketServer (const aSocket : integer);
procedure FFSetIPXSocketClient (const aSocket : integer);
procedure FFSetSPXSocket (const aSocket : integer);
function FFGetIPXSocketServer : integer;
function FFGetIPXSocketClient : integer;
function FFGetSPXSocket : integer;
{$IFDEF KALog}
var
KALog : TffEventLog;
{$ENDIF}
implementation
uses
ffsrbde;
const
DeallocTimeOut = 500;
{ Port constants - define in network-byte order. }
ffc_TCPPort : integer = $6563;
ffc_UDPPortServer : integer = $6563;
ffc_UDPPortClient : integer = $6564;
ffc_IPXSocketServer : integer = $6563;
ffc_IPXSocketClient : integer = $6564;
ffc_SPXSocket : integer = $6565;
{===Helper routines==================================================}
procedure CodeBuffer(var aCode : TffNetMsgCode; var aBuf; aBufLen : integer);
register;
asm
push ebx
push esi
push edi
mov edi, eax
@@ResetCode:
mov ebx, ffc_CodeLength
mov esi, edi
@@NextByte:
mov al, [edx]
xor al, [esi]
mov [edx], al
dec ecx
jz @@Exit
inc edx
dec ebx
jz @@ResetCode
inc esi
jmp @@NextByte
@@Exit:
pop edi
pop esi
pop ebx
end;
{--------}
procedure GenerateCode(aStart : longint; var aCode : TffNetMsgCode);
const
im = 259200;
ia = 7141;
ic = 54773;
var
i : integer;
begin
{Note: routine and constants are from Numerical Recipes in Pascal, page 218}
aStart := aStart mod im;
for i := 0 to pred(ffc_CodeLength) do begin
aStart := ((aStart * ia) + ic) mod im;
aCode[i] := (aStart * 256) div im;
end;
end;
{--------}
procedure CheckWinsockError(const ErrorCode : Integer; const Connecting : Boolean);
{ Rewritten !!.05}
{ When doing mass numbers of connects/disconnects and retrying connections
(see TffWinsockProtocol.Call), certain errors may occur that appear to be
timing-related (i.e., the code doesn't see that the socket is connected
because the event from the Winsock layer has yet to be processed).
WsaEALREADY & WsaEISCONN showed up consistently on Windows 2000.
WsaEINVAL showed up consistently on W95. }
var
TmpCode : Integer;
begin
if (ErrorCode = SOCKET_ERROR) then begin
TmpCode := WinsockRoutines.WSAGetLastError;
if (TmpCode <> 0) and (TmpCode <> WSAEWOULDBLOCK) then
if not (Connecting and
((TmpCode = WsaEALREADY) or
(TmpCode = WsaEISCONN) or
(TmpCode = WsaEINVAL)
)
) then
raise EffWinsockException.CreateTranslate(TmpCode, nil);
end; { if }
end;
{--------}
procedure FFSplitNetAddress(const aAddress : TffNetAddress;
var aLocalName : TffNetName;
var aNetName : TffNetName);
var
PosAt : integer;
begin
PosAt := Pos('@', aAddress);
if (PosAt > 0) then begin
aLocalName := Copy(aAddress, 1, FFMinI(Pred(PosAt), ffcl_NetNameSize)); {!!.06}
aNetName := Copy(aAddress, succ(PosAt), FFMinI(Length(aAddress) - PosAt, ffcl_NetNameSize)); {!!.06}
end
else begin
aLocalName := aAddress;
aNetName := aAddress;
end;
end;
{--------}
procedure FFMakeNetAddress(var aAddress : TffNetAddress;
const aLocalName : TffNetName;
const aNetName : TffNetName);
begin
aAddress := aLocalName;
{Begin !!.03}
{$IFDEF IsDelphi}
if (FFCmpShStr(aLocalName, aNetName, 255) <> 0) then begin
FFShStrAddChar(aAddress, '@');
FFShStrConcat(aAddress, aNetName);
end;
{$ELSE}
if aLocalName <> aNetName then
aAddress := aAddress + '@' + aNetName;
{$ENDIF}
{End !!.03}
end;
{--------}
procedure FFSetTCPPort(const aPort : integer);
begin
if not FFWSInstalled then
raise EffWinsockException.CreateNoData(ffStrResGeneral, fferrWSNoWinsock);
ffc_TCPPort := WinsockRoutines.htons(aPort);
end;
{--------}
procedure FFSetUDPPortServer (const aPort : integer);
begin
if not FFWSInstalled then
raise EffWinsockException.CreateNoData(ffStrResGeneral, fferrWSNoWinsock);
ffc_UDPPortServer := WinsockRoutines.htons(aPort);
end;
{--------}
procedure FFSetUDPPortClient (const aPort : integer);
begin
if not FFWSInstalled then
raise EffWinsockException.CreateNoData(ffStrResGeneral, fferrWSNoWinsock);
ffc_UDPPortClient := WinsockRoutines.htons(aPort);
end;
{--------}
function FFGetTCPPort : integer;
begin
if FFWSInstalled then
Result := WinsockRoutines.ntohs(ffc_TCPPort)
else
Result := 0;
end;
{--------}
function FFGetUDPPortServer : integer;
begin
if FFWSInstalled then
Result := WinsockRoutines.ntohs(ffc_UDPPortServer)
else
Result := 0;
end;
{--------}
function FFGetUDPPortClient : integer;
begin
if FFWSInstalled then
Result := WinsockRoutines.ntohs(ffc_UDPPortClient)
else
Result := 0;
end;
{--------}
procedure FFSetIPXSocketServer (const aSocket : integer);
begin
if not FFWSInstalled then
raise EffWinsockException.CreateNoData(ffStrResGeneral, fferrWSNoWinsock);
ffc_IPXSocketServer := WinsockRoutines.htons(aSocket);
end;
{--------}
procedure FFSetIPXSocketClient (const aSocket : integer);
begin
if not FFWSInstalled then
raise EffWinsockException.CreateNoData(ffStrResGeneral, fferrWSNoWinsock);
ffc_IPXSocketClient := WinsockRoutines.htons(aSocket);
end;
{--------}
procedure FFSetSPXSocket (const aSocket : integer);
begin
if not FFWSInstalled then
raise EffWinsockException.CreateNoData(ffStrResGeneral, fferrWSNoWinsock);
ffc_SPXSocket := WinsockRoutines.htons(aSocket);
end;
{--------}
function FFGetIPXSocketServer : integer;
begin
if FFWSInstalled then
Result := WinsockRoutines.ntohs(ffc_IPXSocketServer)
else
Result := 0;
end;
{--------}
function FFGetIPXSocketClient : integer;
begin
if FFWSInstalled then
Result := WinsockRoutines.ntohs(ffc_IPXSocketClient)
else
Result := 0;
end;
{--------}
function FFGetSPXSocket : integer;
begin
if FFWSInstalled then
Result := WinsockRoutines.ntohs(ffc_SPXSocket)
else
Result := 0;
end;
{====================================================================}
{===TffConnection====================================================}
constructor TffConnection.Create(aOwner : TffBaseCommsProtocol;
aRemoteName : TffNetAddress);
begin
inherited Create;
FFGetMem(FCode, SizeOf(TffNetMsgCode));
FClientID := 0;
FHangingUp := True;
FHangupDone := False; {!!.01}
FHangupLock := TffPadlock.Create; {!!.01}
FOwner := aOwner;
FRemoteName := FFShStrAlloc(aRemoteName);
MaintainLinks := False; {!!.05}
end;
{--------}
destructor TffConnection.Destroy;
begin
FHangupLock.Free;
FFFreeMem(FCode, SizeOf(TffNetMsgCode));
FFShStrFree(FRemoteName);
inherited Destroy;
end;
{--------}
Procedure TffConnection.AddToList(List : TFFList);
begin {do nothing, descendant must do the work}
end;
{--------}
function TffConnection.GetRemoteName : string; {!!.10}
begin
Result := FRemoteName^;
end;
{--------}
procedure TffConnection.ConfirmAlive(SendConfirm : boolean);
begin
FAliveRetries := 0;
FFLLBASE.SetTimer(FLastMsgTimer, FOwner.LastMsgInterval);
FSendConfirm := SendConfirm;
end;
{--------}
procedure TffConnection.DepleteLife;
begin
{$IFDEF KALog}
KALog.WriteStringFmt('DepleteLife, client %d', [ClientID]);
{$ENDIF}
inc(FAliveRetries);
end;
{Begin !!.01}
{--------}
procedure TffConnection.HangupLock;
begin
FHangupLock.Lock;
end;
{--------}
procedure TffConnection.HangupUnlock;
begin
FHangupLock.Unlock;
end;
{End !!.01}
{--------}
procedure TffConnection.InitCode(const aStart : longint);
begin
{ Find the connection associated with this client. }
if (aStart = 0) then begin
FCodeStart := GetTickCount;
if (FCodeStart = 0) then
FCodeStart := $12345678;
end
else
FCodeStart := aStart;
GenerateCode(FCodeStart, FCode^);
end;
{--------}
function TffConnection.IsAlive : boolean;
begin
Result := FAliveRetries < FOwner.KeepAliveRetries;
end;
{--------}
function TffConnection.IsVeryAlive : boolean;
begin
Result := not HasTimerExpired(FLastMsgTimer);
end;
{--------}
function TffConnection.NeedsConfirmSent : boolean;
begin
Result := FSendConfirm;
FSendConfirm := false;
end;
{--------}
procedure TffConnection.RemoveFromList(List : TFFList);
begin {do nothing, descendant must do the work}
end;
{====================================================================}
{===TffBaseCommsProtocol=================================================}
constructor TffBaseCommsProtocol.Create(const aName : TffNetAddress;
aCSType : TffClientServerType);
var
LocalNm : TffNetName;
NetNm : TffNetName;
begin
inherited Create;
FConnLock := TffPadlock.Create;
FCSType := aCSType;
FEventLog := nil;
FKeepAliveInterval := ffc_KeepAliveInterval;
FKeepAliveRetries := ffc_KeepAliveRetries;
FLastMsgInterval := ffc_LastMsgInterval;
FFSplitNetAddress(aName, LocalNm, NetNm);
FLocalName := FFShStrAlloc(LocalNm);
FLogEnabled := false;
cpSetNetName('Local');
FSearchTimeOut := 500;
FStarted := false;
FStartedEvent := TffEvent.Create;
{the net name is set by our descendants}
cpConnList := TffList.Create;
cpIndexByClient := TffList.Create;
cpIndexByClient.Sorted := True;
cpIndexByOSConnector := nil;
{ If this protocol is for a server then create a connection lookup list.
The lookup list serves as an index, allowing us to quickly find a
connection object. This is much faster than doing a sequential search
through the cpConnList. }
if aCSType = csServer then begin
cpIndexByOSConnector := TFFList.Create;
cpIndexByOSConnector.Sorted := True;
end;
end;
{--------}
destructor TffBaseCommsProtocol.Destroy;
begin
FStarted := false;
FConnLock.Free;
if assigned(FStartedEvent) then
FStartedEvent.Free;
FFShStrFree(FLocalName);
FFShStrFree(FNetName);
cpConnList.Free;
cpIndexByClient.Free;
if assigned(cpIndexByOSConnector) then
cpIndexByOSConnector.Free;
inherited Destroy;
end;
{--------}
procedure TffBaseCommsProtocol.Breathe;
var
dummy : pointer;
Msg : TMsg;
begin
if PeekMessage(Msg, FNotifyWindow, 0, 0, PM_NOREMOVE) then begin
while PeekMessage(Msg, FNotifyWindow, 0, 0, PM_REMOVE) do
DispatchMessage(Msg);
end
else begin
dummy := nil;
MsgWaitForMultipleObjects(0, dummy, false, 1, QS_ALLINPUT);
end;
end;
{--------}
function TffBaseCommsProtocol.ClientIDExists(const aClientID : TffClientID) : boolean;
{Rewritten !!.05}
begin
ConnLock;
try
Result := (cpIndexByClient.Index(aClientID) <> -1);
finally
ConnUnlock;
end;
end;
{--------}
function TffBaseCommsProtocol.ConnectionCount : longInt;
begin
Result := 0;
if assigned(cpConnList) then
Result := cpConnList.Count;
end;
{--------}
procedure TffBaseCommsProtocol.ConnLock;
begin
FConnLock.Lock;
end;
{--------}
procedure TffBaseCommsProtocol.ConnUnlock;
begin
FConnLock.Unlock;
end;
{--------}
procedure TffBaseCommsProtocol.cpAddConnection(aConnection : TffConnection);
{Rewritten !!.05}
var
anItem : TffIntListItem;
begin
ConnLock;
try
aConnection.InitCode(0);
cpConnList.Insert(aConnection);
{ Add an entry to the index by client. }
anItem := TffIntListItem.Create(aConnection.ClientID);
anItem.ExtraData := aConnection;
cpIndexByClient.Insert(anItem);
if Assigned(cpIndexByOSConnector) then
aConnection.AddToList(cpIndexByOSConnector);
finally
ConnUnlock;
end;
end;
{--------}
procedure TffBaseCommsProtocol.cpCodeMessage(aConn : TffConnection;
aData : PffByteArray;
aDataLen : longint);
const
LeaveRawLen = 2 * sizeof(longint);
var
aCode : TffNetMsgCode;
begin
if (aDataLen >= LeaveRawLen) then begin
if (PffLongint(aData)^ <> ffnmAttachServer) then begin
aCode := aConn.Code^;
CodeBuffer(aCode, aData^[LeaveRawLen], aDataLen - LeaveRawLen);
end;
end
end;
{--------}
function TffBaseCommsProtocol.cpCreateNotifyWindow : boolean;
begin
FNotifyWindow := 0;
Result := false;
end;
{--------}
procedure TffBaseCommsProtocol.cpDestroyNotifyWindow;
begin
if (FNotifyWindow <> 0) then begin
KillTimer(FNotifyWindow, 1);
{$IFDEF DCC6OrLater} {!!.11}
{$WARN SYMBOL_DEPRECATED OFF}
{$ENDIF}
{$ifdef fpc}
LCLIntf.DeallocateHWnd(FNotifyWindow); //soner
{$else}
DeallocateHWnd(FNotifyWindow);
{$endif}
{$IFDEF DCC6OrLater} {!!.11}
{$WARN SYMBOL_DEPRECATED ON}
{$ENDIF}
end;
end;
{--------}
procedure TffBaseCommsProtocol.cpDoHangUp(aConn : TffConnection);
begin
{Begin !!.01}
aConn.HangupLock;
try
if aConn.HangupDone then
Exit;
{ Are we hanging up on purpose? }
if aConn.HangingUp then begin
{ Yes. Call the OnHangUp event if it is declared. }
if Assigned(FOnHangUp) then
FOnHangUp(Self, aConn.ClientID);
end
{ No. This is an unexpected hangup. Invoke OnConnectionLost if it is
declared. }
else if Assigned(FOnConnectionLost) then
FOnConnectionLost(Self, aConn.ClientID);
aConn.HangupDone := True;
finally
aConn.HangupUnlock;
end;
{End !!.01}
end;
{--------}
procedure TffBaseCommsProtocol.cpDoHeardCall(aConnHandle : longint);
begin
if Assigned(FHeardCall) then
FHeardCall(Self, aConnHandle);
end;
{--------}
procedure TffBaseCommsProtocol.cpPerformShutdown;
begin
cpDestroyNotifyWindow;
end;
{--------}
procedure TffBaseCommsProtocol.cpSetNetName(aName : string);
begin
if assigned(FNetName) then
FFShStrFree(FNetName);
FNetName := FFShStrAlloc(aName);
end;
{--------}
procedure TffBaseCommsProtocol.cpDoReceiveDatagram(const aName : TffNetName;
aData : PffByteArray;
aDataLen : longint);
begin
if Assigned(FReceiveDatagram) then
FReceiveDatagram(Self, aName, aData, aDataLen);
end;
{--------}
function TffBaseCommsProtocol.cpDoReceiveMsg(aConn : TffConnection;
msgData : PffByteArray;
msgDataLen : longInt) : boolean;
begin
{Look out for keep alives}
if (PffLongint(msgData)^ = ffnmCheckConnection) then begin
cpGotCheckConnection(aConn);
Result := true;
Exit;
end;
{process normal FF message}
{$IFDEF KALog}
KALog.WriteStringFmt('RcvMsg, client %d', [aConn.ClientID]);
{$ENDIF}
aConn.ConfirmAlive(false);
{ If this message is too big for us then reject it. }
if msgDataLen > FMaxNetMsgSize then begin
LogStrFmt('Message size %d too large.', [msgDataLen]);
Result := False;
end
{ Otherwise if we have a handler for the message then send the message
to the handler. }
else if Assigned(FReceiveMsg) then begin
cpCodeMessage(aConn, msgData, msgDataLen);
Result := FReceiveMsg(Self, aConn.ClientID, msgData, msgDataLen);
end else
{ Otherwise no handler so just smile. }
Result := true;
end;
{--------}
function TffBaseCommsProtocol.cpExistsConnection(aConnHandle : longint) : boolean;
begin
Result := cpConnList.Exists(aConnHandle);
end;
{--------}
function TffBaseCommsProtocol.cpFindConnection(const aClientID : TffClientID) : Longint;
var
Inx : Longint;
begin
Result := -1;
for Inx := 0 to pred(cpConnList.Count) do
if TffConnection(cpConnList[Inx]).ClientID = aClientID then begin
Result := Inx;
break;
end;
end;
{--------}
function TffBaseCommsProtocol.cpGetConnection(const aClientID : TffClientID) : TffConnection;
{ Modified !!.05}
var
Inx : integer;
begin
{ Note: It is possible for a newly-attached client to send another request to
the server before the server has had a chance to update the new
client's server-side clientID. So we use a lock to prevent this
from happening. }
ConnLock;
try
Inx := cpIndexByClient.Index(aClientID);
if (Inx = -1) then
Result := nil
else
Result := TffConnection(TffIntListItem(cpIndexByClient[Inx]).ExtraData);
finally
ConnUnlock;
end;
end;
{--------}
function TffBaseCommsProtocol.cpGetConnectionIDs(const anIndex : longInt) : TffClientID;
{Begin !!.01}
var
aConn : TffConnection;
begin
aConn := TffConnection(cpConnList[anIndex]);
if aConn = nil then
Result := 0
else
Result := TffConnection(cpConnList[anIndex]).ClientID;
{End !!.01}
end;
{--------}
procedure TffBaseCommsProtocol.cpGotCheckConnection(aConn : TffConnection);
begin
{Reset keepalives}
if assigned(aConn) then begin
{$IFDEF KALog}
KALog.WriteStringFmt('RcvKA, client %d', [aConn.ClientID]);
{$ENDIF}
aConn.ConfirmAlive(true);
end;
end;
{--------}
procedure TffBaseCommsProtocol.cpRemoveConnection(aClientID : TffClientID);
var
Inx : integer;
aConn : TffConnection;
begin
{Begin !!.05}
ConnLock;
try
Inx := cpIndexByClient.Index(aClientID);
{ Did we find the connection in the index? }
if (Inx >= 0) then begin
{ Yes. Remove the connection from the index and from the connection
list. }
aConn := TffConnection(cpIndexByClient[Inx]).ExtraData;
cpIndexByClient.DeleteAt(Inx);
cpConnList.Remove(aConn);
if assigned(cpIndexByOSConnector) then
aConn.RemoveFromList(cpIndexByOSConnector);
aConn.Free;
end
else begin
{ No. It may be that we have encountered a client that could not
successfully connect. We have an entry in the connection list but not
in the index. Do a sequential search for the client. }
Inx := cpFindConnection(aClientID);
if Inx >= 0 then begin
aConn := TffConnection(cpConnList[Inx]);
cpConnList.RemoveAt(Inx);
aConn.Free;
end;
end;
finally
ConnUnlock;
end;
{End !!.05}
end;
{--------}
procedure TffBaseCommsProtocol.cpTimerTick;
var
Inx : integer;
Conn : TffConnection;
KAMsg : longint;
begin
{Begin !!.05}
ConnLock;
try
KAMsg := ffnmCheckConnection;
for Inx := pred(cpConnList.Count) downto 0 do begin
Conn := TffConnection(cpConnList[Inx]);
with Conn do begin
if (not Conn.FHangupLock.Locked) and (not IsAlive) then begin {!!.11}
{$IFDEF KALog}
KALog.WriteStringFmt('Hangup, client %d', [Conn.ClientID]);
{$ENDIF}
Conn.HangingUp := False; {!!.06}
HangUp(Conn);
end
else if NeedsConfirmSent or (not IsVeryAlive) then begin
{$IFDEF KALog}
KALog.WriteStringFmt('Send KA, client %d', [Conn.ClientID]);
{$ENDIF}
SendMsg(ClientID, @KAMsg, sizeof(KAMsg), False); {!!.06}
DepleteLife;
end;
end;
end;
finally
ConnUnlock;
end;
{End !!.05}
end;
{--------}
function TffBaseCommsProtocol.GetLocalName : string; {!!.10}
begin
if Assigned(FLocalName) then
Result := FLocalName^
else
Result := '';
end;
{--------}
function TffBaseCommsProtocol.GetNetName : string; {!!.10}
begin
if Assigned(FNetName) then
Result := FNetName^
else
Result := '';
end;
{--------}
function TffBaseCommsProtocol.GetCodeStart(const aClientID : TffClientID) : integer;
var
aConn : TffConnection;
anItem : TffIntListItem;
begin
{ Assumption: Connection lists locked via ConnLock at a higher level. }
Result := 0;
{ Find the connection associated with this client. }
anItem := TffIntListItem(cpIndexByClient[cpIndexByClient.Index(aClientID)]);
if assigned(anItem) then begin
aConn := TffConnection(anItem.ExtraData);
Result := aConn.CodeStart;
end;
end;
{--------}
class function TffBaseCommsProtocol.GetProtocolName : string;
begin
{ return nothing at this level }
Result := '';
end;
{--------}
procedure TffBaseCommsProtocol.HangUpByClientID(aClientID : TffClientID);
var
aConn : TffConnection;
begin
aConn := cpGetConnection(aClientID);
if assigned(aConn) then begin
aConn.HangingUp := True;
HangUp(aConn);
end;
end;
{Begin !!.01}
{--------}
procedure TffBaseCommsProtocol.HangupDone(aClientID : TffClientID);
var
aConn : TffConnection;
begin
aConn := cpGetConnection(aClientID);
if assigned(aConn) then
aConn.HangupDone := True;
end;
{--------}
function TffBaseCommsProtocol.HangupIsDone(aClientID : TffClientID) : Boolean;
var
aConn : TffConnection;
begin
Result := False;
aConn := cpGetConnection(aClientID);
if assigned(aConn) then
Result := aConn.HangupDone;
end;
{--------}
procedure TffBaseCommsProtocol.HangupLock(aClientID : TffClientID);
var
aConn : TffConnection;
begin
aConn := cpGetConnection(aClientID);
if assigned(aConn) then
aConn.HangupLock;
end;
{--------}
procedure TffBaseCommsProtocol.HangupUnlock(aClientID : TffClientID);
var
aConn : TffConnection;
begin
aConn := cpGetConnection(aClientID);
if assigned(aConn) then
aConn.HangupUnlock;
end;
{End !!.01}
{--------}
procedure TffBaseCommsProtocol.InitCode(const aClientID : TffClientID;
const aStart : longint);
var
aConn : TffConnection;
anItem : TffIntListItem;
begin
{ Find the connection associated with this client. }
anItem := TffIntListItem(cpIndexByClient[cpIndexByClient.Index(aClientID)]);
if assigned(anItem) then begin
aConn := TffConnection(anItem.ExtraData);
aConn.InitCode(aStart);
end;
end;
{--------}
procedure TffBaseCommsProtocol.ResetKeepAliveTimer;
begin
if (FNotifyWindow <> 0) then begin
{$IFDEF KALog}
KALog.WriteStringFmt('ResetKeepAliveTimer: protocol %d', [Longint(Self)]);
{$ENDIF}
KillTimer(FNotifyWindow, 1);
Windows.SetTimer(FNotifyWindow, 1, FKeepAliveInterval, nil); {!!.05}
end;
end;
{--------}
procedure TffBaseCommsProtocol.Shutdown;
begin
if IsStarted then begin
cpPerformShutdown;
FStarted := false;
end;
end;
{--------}
procedure TffBaseCommsProtocol.StartUp;
begin
if not IsStarted then begin
cpPerformStartUp;
FStarted := true;
FStartedEvent.SignalEvent;
end;
end;
{--------}
class function TffBaseCommsProtocol.Supported : boolean;
begin
Result := True;
end;
{--------}
procedure TffBaseCommsProtocol.UpdateClientID(const oldClientID,
newClientID : TffClientID);
var
aConn : TffConnection;
anItem : TffIntListItem;
begin
{Begin !!.05}
ConnLock;
try
anItem := TffIntListItem(cpIndexByClient[cpIndexByClient.Index(oldClientID)]);
if assigned(anItem) then begin
aConn := anItem.ExtraData;
aConn.ClientID := newClientID;
{ Get rid of the old index entry; as a side effect, anItem should be
freed. }
cpIndexByClient.Delete(oldClientID);
{ Create a new entry for the index. }
anItem := TffIntListItem.Create(newClientID);
anItem.ExtraData := aConn;
cpIndexByClient.Insert(anItem);
end;
finally
ConnUnlock;
end;
{End !!.05}
end;
{--------}
procedure TffBaseCommsProtocol.LogStr(const aMsg : string);
begin
if FLogEnabled and assigned(FEventLog) then
FEventLog.WriteSTring(format('%s: %s',
[Self.GetProtocolName, aMsg]));
end;
{--------}
procedure TffBaseCommsProtocol.LogStrFmt(const aMsg : string;
args : array of const);
begin
if FLogEnabled and assigned(FEventLog) then
LogStr(format(aMsg, args));
end;
{====================================================================}
{===TffWinsockConnection=============================================}
constructor TffWinsockConnection.Create(aOwner : TffBaseCommsProtocol;
aRemoteName : TffNetAddress;
aSocket : TffwsSocket;
aFamily : TffWinsockFamily;
aNotifyWnd : HWND);
var
NagelOn : Bool;
begin
inherited Create(aOwner, aRemoteName);
FHangingUp := False;
{ Note that we are overriding the initial value of FHangingUp on purpose. }
FSocket := aSocket;
FFamily := aFamily;
if (aFamily = wfTCP) then begin
FFWSGetSocketOption(aSocket, IPPROTO_TCP, TCP_NODELAY, NagelOn, sizeof(NagelOn));
if NagelOn then begin
NagelOn := false;
FFWSSetSocketOption(aSocket, IPPROTO_TCP, TCP_NODELAY, NagelOn, sizeof(NagelOn));
end;
end;
FFWSSetSocketOption(aSocket, SOL_SOCKET, So_RCVBUF, ffc_TCPRcvBuf,
sizeof(ffc_TCPRcvBuf));
FFWSSetSocketOption(aSocket, SOL_SOCKET, So_SNDBUF, ffc_TCPSndBuf,
sizeof(ffc_TCPSndBuf));
FFWSGetSocketOption(aSocket, SOL_SOCKET, So_RCVBUF, wscRcvBuf,
sizeof(wscRcvBuf));
FFWSGetSocketOption(aSocket, SOL_SOCKET, So_SNDBUF, wscSndBuf,
sizeof(wscSndBuf));
wscNotifyWnd := aNotifyWnd;
// wscPortal := TffReadWritePortal.Create; {Deleted !!.05}
GetMem(wscRcvBuffer, ffc_MaxWinsockMsgSize);
wscPacketHead := Nil;
wscPacketTail := Nil;
wscIsSending := False;
end;
{--------}
destructor TffWinsockConnection.Destroy;
var
aPacket : PffwscPacket;
begin
HangupLock; {!!.05}
// wscPortal.BeginWrite; {Deleted !!.05}
try
try
FFWSDestroySocket(Socket);
except
end;
while wscPacketHead <> Nil do begin
aPacket := wscPacketHead^.lpNext;
ffFreeMem(wscPacketHead^.lpData, wscPacketHead^.dwLength);
ffFreeMem(wscPacketHead, sizeof(TffwscPacket));
wscPacketHead := aPacket;
end;
FreeMem(wscRcvBuffer, ffc_MaxWinsockMsgSize);
finally
HangupUnlock; {!!.05}
// wscPortal.EndWrite; {Deleted !!.05}
// wscPortal.Free; {Deleted !!.05}
end;
inherited Destroy;
end;
{--------}
Procedure TffWinsockConnection.AddToList(List : TFFList);
var
T : TffIntListItem;
begin {add a list entry to allow socket lookups}
T := TffIntListItem.Create(Socket);
T.ExtraData := Self;
List.Insert(T);
end;
{--------}
Procedure TffWinsockConnection.RemoveFromList(List : TFFList);
begin
List.Delete(FSocket);
end;
{--------}
function TffWinsockConnection.Send(aData : PffByteArray;
aDataLen : longint;
aDataStart : longint;
var aBytesSent : longint;
aConnLock : Boolean) : integer; {!!.06}
var
BytesSent : longint;
PacketBuffer : PffwscPacket;
begin
if aConnLock then {!!.06}
HangupLock; {!!.05}
// wscPortal.BeginWrite; {Deleted !!.05}
try
Result := 0;
if (aDataLen-aDataStart) > 0 then begin
{Add the data packet to the wscPacketList }
ffGetMem(PacketBuffer,sizeof(TffwscPacket));
ffGetMem(PacketBuffer^.lpData, aDataLen);
PacketBuffer^.dwLength := aDataLen;
PacketBuffer^.dwStart := aDataStart;
Move(aData^[0], PacketBuffer^.lpData^, PacketBuffer^.dwLength);
Owner.cpCodeMessage(Self, PacketBuffer^.lpData, PacketBuffer^.dwLength);
PacketBuffer^.lpNext := Nil;
{Add the packet to the end of the list }
if not assigned(wscPacketHead) then
wscPacketHead := PacketBuffer
else if assigned(wscPacketTail) then
wscPacketTail^.lpNext := PacketBuffer;
wscPacketTail := PacketBuffer;
aBytesSent := 0; {!!.06}
// aBytesSent := aDataLen-aDataStart; {Always report all bytes sent} {Deleted !!.06}
end;
if (not wscIsSending) and Assigned(wscPacketHead) then begin
{now try to send some data}
try
{send the first waiting data packet}
BytesSent := WinsockRoutines.send(Socket,
wscPacketHead^.lpData^[wscPacketHead^.dwStart],
wscPacketHead^.dwLength-wscPacketHead^.dwStart,
0);
except
BytesSent := SOCKET_ERROR;
end;
if (BytesSent = SOCKET_ERROR) then begin
{There was an error sending }
Result := WinsockRoutines.WSAGetLastError;
if (Result = WSAEWOULDBLOCK) then begin
{ Mark this connection as blocked and leave the Packet on the list. }
wscIsSending := True;
// Result := 0; {Deleted !!.06}
end
{Begin !!.06}
else if Result = 0 then
{ If no error code returned then reset the Result to -1 so that we
break out of the send loop, avoiding a re-add of the current
packet to the packet list. }
Result := -1;
{End !!.06}
end else if BytesSent < (wscPacketHead^.dwLength - wscPacketHead^.dwStart) then begin
{ we didn't send the whole thing, so re-size the data packet}
inc(wscPacketHead^.dwStart, BytesSent);
inc(aBytesSent, BytesSent); {!!.06}
{ now try sending the remaining data again }
Result := Send(nil, 0, 0, aBytesSent, aConnLock); {!!.06}
end else begin
{we sent the packet, so remove it and continue }
ffFreeMem(wscPacketHead^.lpData, wscPacketHead^.dwLength);
PacketBuffer := wscPacketHead;
wscPacketHead := wscPacketHead^.lpNext;
if not Assigned(wscPacketHead) then
wscPacketTail := Nil;
ffFreeMem(PacketBuffer, sizeof(TffwscPacket));
inc(aBytesSent, BytesSent); {!!.11}
Result := 0;
end;
end;
finally
if aConnLock then {!!.06}
HangupUnlock; {!!.05}
// wscPortal.EndWrite; {Deleted !!.05}
end;
end;
{--------}
procedure TffWinsockConnection.StartReceive;
begin
FFWSAsyncSelect(Socket, wscNotifyWnd,
FD_READ or FD_WRITE or FD_CLOSE);
end;
{====================================================================}
{===TffWinsockProtocol===============================================}
constructor TffWinsockProtocol.Create(const aName : TffNetAddress;
aCSType : TffClientServerType);
begin
{make sure Winsock is installed}
if not FFWSInstalled then
raise EffWinsockException.CreateNoData(ffStrResGeneral, fferrWSNoWinsock);
{let our ancestor create itself}
inherited Create(aName, aCSType);
FCollectingServerNames := false;
FDatagramPadlock := TffPadlock.Create;
FMaxNetMsgSize := ffc_MaxWinsockMsgSize;
FServerNames := TStringList.Create;
FServerNames.Sorted := True;
FServerNames.Duplicates := dupIgnore;
{set the sockets we use to default values}
wspListenSocket := INVALID_SOCKET;
wspRcvDatagramSocket := INVALID_SOCKET;
{allocate a receive datagram buffer}
GetMem(wspRcvDGBuffer, ffc_MaxDatagramSize);
end;
{--------}
destructor TffWinsockProtocol.Destroy;
begin
if assigned(FServerNames) then
FServerNames.Free;
if assigned(FDatagramPadlock) then
FDatagramPadlock.Free;
FFWSDestroySocket(wspListenSocket);
FFWSDestroySocket(wspRcvDatagramSocket);
inherited Destroy;
FFShStrFree(FNetName);
FreeMem(wspRcvDGBuffer, ffc_MaxDatagramSize);
end;
{--------}
function TffWinsockProtocol.Call(const aServerName : TffNetName;
var aClientID : TffClientID;
const timeout : longInt) : TffResult;
var
NewSocket : TffwsSocket;
Conn : TffWinsockConnection;
SASize : integer;
AddrFamily : integer;
Protocol : integer;
RemSockAddr : TffwsSockAddr;
aNetName : TffNetName;
T : TffTimer; {!!.05}
StartTime : DWORD; {!!.05}
begin
Result := DBIERR_NONE;
{servers don't call}
if (CSType = csServer) then
raise EffCommsException.CreateNoData(ffStrResGeneral, fferrCommsCannotCall);
{ If no servername then we cannot connect. }
if (aServerName = '') then begin
Result := DBIERR_SERVERNOTFOUND;
Exit;
end;
{either create a socket address record for TCP...}
if (Family = wfTCP) then begin
AddrFamily := AF_INET;
Protocol := 0;
SASize := sizeof(TffwsSockAddrIn);
FillChar(RemSockAddr, SASize, 0);
with RemSockAddr.TCP do begin
sin_family := PF_INET;
sin_port := ffc_TCPPort;
if FFWSCvtStrToAddr(aServerName, sin_addr) then
// aNetName := FFWSGetRemoteNameFromAddr(sin_addr)
else begin
if not FFWSGetRemoteHost(aServerName, aNetName, sin_addr) then begin
Result := DBIERR_SERVERNOTFOUND; {!!.06}
Exit;
end;
end;
end;
end
{or for IPX...}
else {if (Family = wfIPX) then} begin
AddrFamily := AF_IPX;
Protocol := NSPROTO_SPX;
SASize := sizeof(TffwsSockAddrIPX);
FillChar(RemSockAddr, SASize, 0);
with RemSockAddr.IPX do begin
sipx_family := PF_IPX;
if not FFWSCvtStrToIPXAddr(aServerName,
sipx_netnum,
sipx_nodenum) then
Exit;
sipx_socket := ffc_SPXSocket;
end;
end;
{open a call socket}
NewSocket := FFWSCreateSocket(AddrFamily, SOCK_STREAM, Protocol);
try
{set the socket to non-blocking mode}
FFWSAsyncSelect(NewSocket, FNotifyWindow, FD_CONNECT);
{try and connect}
wspWaitingForConnect := true;
CheckWinsockError(
WinsockRoutines.connect(NewSocket, RemSockAddr, SASize), False);
{Begin !!.05}
// wspWaitForConnect(timeout, NewSocket);
StartTime := GetTickCount;
SetTimer(T, timeout);
while wspWaitingForConnect and (not HasTimerExpired(T)) do begin
if (GetTickCount - StartTime) > ffc_ConnectRetryTimeout then begin
CheckWinsockError(WinsockRoutines.connect(NewSocket, RemSockAddr,
SASize), True);
Starttime := GetTickCount;
end;
Breathe;
end;
{End !!.05}
{if we connected...}
if not wspWaitingForConnect then begin
{create a new connection}
Conn := TffWinsockConnection.Create(Self, aNetName, NewSocket, Family,
FNotifyWindow);
Conn.ClientID := Conn.Handle;
aClientID := Conn.Handle;
cpAddConnection(Conn);
Conn.StartReceive;
end
else begin {we didn't connect}
FFWSDestroySocket(NewSocket);
Result := DBIERR_SERVERNOTFOUND;
end;
except
FFWSDestroySocket(NewSocket);
raise;
end;{try..except}
end;
{--------}
procedure TffWinsockProtocol.cpDoReceiveDatagram(const aName : TffNetName;
aData : PffByteArray;
aDataLen : longint);
var
Addr : TffNetAddress; { sender }
Datagram : PffnmServerNameReply absolute aData; { sender }
Msg : PffnmRequestServerName absolute aData; { listener }
Reply : TffnmServerNameReply; { listener }
begin
inherited cpDoReceiveDatagram(aName, aData, aDataLen);
FDatagramPadlock.Lock;
try
{ If we are on the sending side, waiting for server names to roll in
then get the server's reply and add it to our list of server names. }
if FCollectingServerNames then begin
if assigned(aData) and (Datagram^.MsgID = ffnmServerNameReply) then begin
FFMakeNetAddress(Addr, Datagram^.ServerLocalName, aName);
FServerNames.Add(Addr);
end;
end else
{ Otherwise, we are on the listening side and a client is asking us to
identify ourself. }
if (aDataLen = sizeof(TffnmRequestServerName)) and
(Msg^.MsgID = ffnmRequestServerName) then begin
{send a message back to the caller with our name}
Reply.MsgID := ffnmServerNameReply;
Reply.ServerLocalName := LocalName;
Reply.ServerNetName := NetName;
SendDatagram(aName, @Reply, sizeof(Reply));
end;
finally
FDatagramPadlock.Unlock;
end;
end;
{--------}
procedure TffWinsockProtocol.cpPerformStartUp;
var
AddrFamily : integer;
Protocol : integer;
SASize : integer;
SockAddr : TffwsSockAddr;
begin
{create our notify window}
if not cpCreateNotifyWindow then begin
LogStr('Could not create notification window.');
raise EffCommsException.CreateNoData(ffStrResGeneral, fferrCommsNoWinRes);
end;
{create and bind the listen socket if we're a server; for a client,
we never would listen}
if (CSType = csServer) then begin
{==the listen socket==}
{create a socket address record}
if (Family = wfTCP) then begin
AddrFamily := AF_INET;
Protocol := 0;
SASize := sizeof(TffwsSockAddrIn);
FillChar(SockAddr, SASize, 0);
with SockAddr.TCP do begin
sin_family := PF_INET;
sin_port := ffc_TCPPort;
sin_addr := wspLocalInAddr;
end;
end
else {if (Family = wfIPX) then} begin
AddrFamily := AF_IPX;
Protocol := NSPROTO_SPX;
SASize := sizeof(TffwsSockAddrIPX);
FillChar(SockAddr, SASize, 0);
with SockAddr.IPX do begin
sipx_family := PF_IPX;
sipx_netnum := wspLocalIPXNetNum;
sipx_nodenum := wspLocalIPXAddr;
sipx_socket := ffc_SPXSocket;
end;
end;
{open a listen socket}
wspListenSocket := FFWSCreateSocket(AddrFamily, SOCK_STREAM, Protocol);
{bind the socket to the address}
CheckWinsockError(
WinsockRoutines.bind(wspListenSocket, SockAddr, SASize), False);
end;
end;
{--------}
procedure TffWinsockProtocol.GetServerNames(aList : TStrings; const timeout : longInt);
var
TotalTimer : TffTimer;
NameReq : TffnmRequestServerName;
begin
if not assigned(aList) then
exit;
{ Open and prepare a UDP socket. }
ReceiveDatagram;
FCollectingServerNames := true;
try
aList.Clear;
FServerNames.Clear;
NameReq.MsgID := ffnmRequestServerName;
SetTimer(TotalTimer, timeout); {!!.13}
SendDatagram('', @NameReq, sizeOf(NameReq));
repeat
Breathe;
until HasTimerExpired(TotalTimer);
aList.Assign(FServerNames);
finally
FCollectingServerNames := false;
StopReceiveDatagram;
end;
end;
{--------}
procedure TffWinsockProtocol.HangUp(aConn : TffConnection);
begin
cpDoHangUp(aConn);
cpRemoveConnection(aConn.ClientID);
end;
{--------}
procedure TffWinsockProtocol.Listen;
begin
{clients don't listen}
if (CSType = csClient) then
raise EffCommsException.CreateNoData(ffStrResGeneral, fferrCommsCantListen);
{start listening, if not doing so already}
if not wspListening then begin
FFWSAsyncSelect(wspListenSocket, FNotifyWindow, FD_ACCEPT);
CheckWinsockError(WinsockRoutines.listen(wspListenSocket, SOMAXCONN), False);
wspListening := true;
end;
end;
{--------}
procedure TffWinsockProtocol.ReceiveDatagram;
var
AddrFamily : integer;
Protocol : integer;
SASize : integer;
BCastOn : Bool;
SockAddr : TffwsSockAddr;
begin
if not wspReceivingDatagram then begin
{create and bind the receive datagram socket}
{create a socket address record}
if (Family = wfTCP) then begin
AddrFamily := AF_INET;
Protocol := 0;
SASize := sizeof(TffwsSockAddrIn);
FillChar(SockAddr, SASize, 0);
with SockAddr.TCP do begin
sin_family := PF_INET;
if (CSType = csClient) then
sin_port := ffc_UDPPortClient
else
sin_port := ffc_UDPPortServer;
sin_addr := wspLocalInAddr;
end;
end
else {if (Family = wfIPX) then} begin
AddrFamily := AF_IPX;
Protocol := NSPROTO_IPX;
SASize := sizeof(TffwsSockAddrIPX);
FillChar(SockAddr, SASize, 0);
with SockAddr.IPX do begin
sipx_family := PF_IPX;
sipx_netnum := wspLocalIPXNetNum;
sipx_nodenum := wspLocalIPXAddr;
if (CSType = csClient) then
sipx_socket := ffc_IPXSocketClient
else
sipx_socket := ffc_IPXSocketServer;
end;
end;
{open a receivedatagram socket}
wspRcvDatagramSocket := FFWSCreateSocket(AddrFamily,
SOCK_DGRAM,
Protocol);
{make sure the socket can do broadcasts (req for IPX)}
if (Family = wfIPX) then begin
BCastOn := true;
FFWSSetSocketOption(wspRcvDatagramSocket, SOL_SOCKET, SO_BROADCAST,
BCastOn, sizeof(BCastOn));
end;
{bind the socket to the address}
CheckWinsockError(
WinsockRoutines.bind(wspRcvDatagramSocket, SockAddr, SASize), False);
FFWSAsyncSelect(wspRcvDatagramSocket, FNotifyWindow, FD_READ or FD_WRITE);
wspReceivingDatagram := true;
end;
end;
{--------}
procedure TffWinsockProtocol.SendDatagram(const aName : TffNetName;
aData : PffByteArray;
aDataLen : longint);
var
SockAddr : TffwsSockAddr;
Socket : TffwsSocket;
SASize : integer;
BCastOn : Bool;
NetName : TffNetName;
begin
{create a send datagram socket}
if (Family = wfTCP) then begin
Socket := FFWSCreateSocket(AF_INET, SOCK_DGRAM, 0);
end
else {Family <> wfTCP} begin
Socket := FFWSCreateSocket(AF_IPX, SOCK_DGRAM, NSPROTO_IPX);
end;
try
{create the socket address to bind to}
if (aName = '') then begin {a broadcast message}
{create a socket address record}
if (Family = wfTCP) then begin
SASize := sizeof(TffwsSockAddrIn);
FillChar(SockAddr, SASize, 0);
with SockAddr.TCP do begin
sin_family := PF_INET;
if (CSType = csClient) then
sin_port := ffc_UDPPortServer
else
sin_port := ffc_UDPPortClient;
sin_addr := INADDR_BROADCAST;
end;
end
else {Family <> wfTCP} begin
SASize := sizeof(TffwsSockAddrIPX);
FillChar(SockAddr, SASize, 0);
with SockAddr.IPX do begin
sipx_family := PF_IPX;
FillChar(sipx_nodenum, sizeof(sipx_nodenum), $FF);
if (CSType = csClient) then
sipx_socket := ffc_IPXSocketServer
else
sipx_socket := ffc_IPXSocketClient;
end;
end;
{make sure the socket can do broadcasts}
BCastOn := true;
FFWSSetSocketOption(Socket, SOL_SOCKET, SO_BROADCAST, BCastOn, sizeof(BCastOn));
end
else begin {a specific target}
{create a socket address record}
if (Family = wfTCP) then begin
SASize := sizeof(TffwsSockAddrIn);
FillChar(SockAddr, SASize, 0);
with SockAddr.TCP do begin
sin_family := PF_INET;
if (CSType = csClient) then
sin_port := ffc_UDPPortServer
else
sin_port := ffc_UDPPortClient;
if not FFWSCvtStrToAddr(aName, sin_addr) then
if not FFWSGetRemoteHost(aName, NetName, sin_addr) then
Exit;
end;
end
else {Family <> wfTCP} begin
SASize := sizeof(TffwsSockAddrIPX);
FillChar(SockAddr, SASize, 0);
with SockAddr.IPX do begin
sipx_family := PF_IPX;
if not FFWSCvtStrToIPXAddr(aName, sipx_netnum, sipx_nodenum) then
Exit;
if (CSType = csClient) then
sipx_socket := ffc_IPXSocketServer
else
sipx_socket := ffc_IPXSocketClient;
end;
end;
end;
CheckWinsockError(
WinsockRoutines.sendto(Socket, aData^, aDataLen, 0, SockAddr, SASize),
False);
finally
FFWSDestroySocket(Socket);
end;{try.finally}
end;
{--------}
function TffWinsockProtocol.SendMsg(aClientID : TffClientID;
aData : PffByteArray;
aDataLen : longint;
aConnLock : Boolean) : TffResult; {!!.06}
var
Conn : TffWinsockConnection;
SendResult : integer;
BytesSent : longint;
SentSoFar : longint;
DataPtr : PffByteArray; {!!.06}
DataLen : Longint; {!!.06}
TimerExpired : Boolean; {!!.06}
begin
Result := DBIERR_NONE;
Conn := TffWinsockConnection(cpGetConnection(aClientID));
if Assigned(Conn) then begin
DataPtr := aData; {!!.06}
DataLen := aDataLen; {!!.06}
SentSoFar := 0;
while (SentSoFar < DataLen) do begin
SendResult := Conn.Send(DataPtr, DataLen, SentSoFar, BytesSent, {!!.06}
aConnLock); {!!.06}
if (SendResult = WSAEWOULDBLOCK) then begin
{Begin !!.06}
TimerExpired := wspWaitForSendToUnblock;
{ The connection has the packet already on its list, waiting to be
resent. Reset the data pointer & length so that the connection
does not add a duplicate packet to its list. }
DataPtr := nil;
DataLen := 0;
{ The connection may have been killed (hung up), so recheck. }
Conn := TffWinsockConnection(cpGetConnection(aClientID));
if Conn = nil then
Exit
else if TimerExpired then begin
wspWaitingForSendToUnblock := False;
Conn.IsSending := False;
end;
{End !!.06}
end
else if (SendResult <> 0) then begin
LogStrFmt('Unhandled Winsock Exception %d', [SendResult]);
Result := SendResult;
// Conn.HangingUp := True; {Deleted !!.06}
// HangUp(Conn); {Deleted !!.06}
exit;
end
else begin
inc(SentSoFar, BytesSent);
end;
end; { while }
end else
Result := fferrConnectionLost;
end;
{--------}
procedure TffWinsockProtocol.SetFamily(F : TffWinsockFamily);
var
LocalHost : TffNetName;
begin
if (FNetName <> nil) then
FFShStrFree(FNetName);
FFamily := F;
if (F = wfTCP) then begin
{get our name and address}
if not FFWSGetLocalHostByNum(ffc_TCPInterface, LocalHost,
wspLocalInAddr) then
raise EffWinsockException.CreateNoData(ffStrResGeneral, fferrWSNoLocalAddr);
cpSetNetName(FFWSCvtAddrToStr(wspLocalInAddr));
end
else if (F = wfIPX) then begin
{get our IPX address}
if not FFWSGetLocalIPXAddr(wspLocalIPXNetNum, wspLocalIPXAddr) then
raise EffWinsockException.CreateNoData(ffStrResGeneral, fferrWSNoLocalAddr);
cpSetNetName(FFWSCvtIPXAddrToStr(wspLocalIPXNetNum, wspLocalIPXAddr));
end;
end;
{--------}
procedure TffWinsockProtocol.StopReceiveDatagram;
begin
if wspReceivingDatagram then begin
FFWSDestroySocket(wspRcvDatagramSocket);
wspRcvDatagramSocket := INVALID_SOCKET;
wspReceivingDatagram := false;
end;
end;
{--------}
procedure TffWinsockProtocol.wspConnectCompleted(aSocket : TffwsSocket);
begin
wspWaitingForConnect := false;
end;
{--------}
function TffWinsockProtocol.cpCreateNotifyWindow : boolean;
begin
{$IFDEF DCC6OrLater} {!!.11}
{$WARN SYMBOL_DEPRECATED OFF}
{$ENDIF}
{$ifdef fpc}
FNotifyWindow := LCLIntf.AllocateHWnd(wspWSAEventCompleted); //soner
{$else}
FNotifyWindow := AllocateHWnd(wspWSAEventCompleted);
{$endif}
{$IFDEF DCC6OrLater} {!!.11}
{$WARN SYMBOL_DEPRECATED ON}
{$ENDIF}
Result := FNotifyWindow <> 0;
if Result then begin
{$IFDEF KALog}
KALog.WriteStringFmt('Winsock.cpCreateNotifyWindow: protocol %d',
[Longint(Self)]);
{$ENDIF}
Windows.SetTimer(FNotifyWindow, 1, FKeepAliveInterval, nil); {!!.05}
end;
end;
{--------}
function TffWinsockProtocol.wspGetConnForSocket(aSocket : TffwsSocket) : TffWinsockConnection;
var
Inx : integer;
T : TffIntListItem;
begin
ConnLock;
try
{ If indexing connections then use the index to find the connection. }
if Assigned(cpIndexByOSConnector) then begin
T := TffIntListItem(cpIndexByOSConnector.Items[cpIndexByOSConnector.Index(aSocket)]);
if T = Nil then
Result := Nil
else
Result := T.ExtraData;
exit;
end;
for Inx := 0 to pred(cpConnList.Count) do begin
Result := TffWinsockConnection(cpConnList[Inx]);
if (Result.Socket = aSocket) then
Exit;
end;
finally
ConnUnlock;
end;
Result := nil;
end;
{--------}
procedure TffWinsockProtocol.wspHangupDetected(aSocket : TffwsSocket);
{Rewritten !!.06}
var
Conn : TffWinsockConnection;
begin
Conn := wspGetConnForSocket(aSocket);
if (Conn <> nil) then begin
Conn.HangingUp := False;
HangUp(Conn);
end;
end;
{--------}
procedure TffWinsockProtocol.wspListenCompleted(aSocket : TffwsSocket);
var
NewSocket : TffwsSocket;
SocketAddr : TffwsSockAddr;
AddrLen : integer;
Conn : TffWinsockConnection;
RemoteName : TffNetName;
WasAdded : boolean;
begin
AddrLen := sizeof(SocketAddr);
NewSocket := WinsockRoutines.accept(aSocket, SocketAddr, AddrLen);
if (NewSocket <> INVALID_SOCKET) then begin
{a listen event has been accepted, create a connection}
WasAdded := false;
Conn := nil;
try
RemoteName := ''; {!!!!}
{ When we first create this connection, we don't have a clientID so
we temporarily use the connection's handle. There is also a temporary
clientID on the client-side of things.
When the client is given a real clientID, the temporary clientIDs on
both client and server are replaced with the true clientID. }
Conn := TffWinsockConnection.Create(Self, RemoteName, NewSocket, Family,
FNotifyWindow);
Conn.ClientID := Conn.Handle;
// Conn.InitCode(0); {Deleted !!.05}
cpAddConnection(Conn);
WasAdded := True; {!!.03}
Conn.StartReceive;
cpDoHeardCall(Conn.ClientID);
except
if WasAdded then
cpRemoveConnection(Conn.ClientID);
raise;
end;
end;
end;
{--------}
procedure TffWinsockProtocol.wspProcessCompletedWSACall(WParam, LParam : longint);
begin
{check the error code}
if (WSAGetSelectError(LParam) <> 0) then
begin
wspHangupDetected(TffwsSocket(WParam));
wspWaitingForSendToUnblock := false;
Exit;
end;
{check for event completion (note case is in numeric sequence)}
case WSAGetSelectEvent(LParam) of
FD_READ :
wspReceiveCompleted(TffwsSocket(WParam));
FD_WRITE :
wspSendMsgCompleted(TffwsSocket(WParam));
FD_OOB :
{do nothing};
FD_ACCEPT :
wspListenCompleted(TffwsSocket(WParam));
FD_CONNECT :
wspConnectCompleted(TffwsSocket(WParam));
FD_CLOSE :
wspHangupDetected(TffwsSocket(WParam));
end;{case}
end;
{--------}
procedure TffWinsockProtocol.wspSendMsgCompleted(aSocket : TffwsSocket);
var
SocketType : integer;
Conn : TffWinsockConnection;
dummy : longint;
begin
wspWaitingForSendToUnblock := false;
SocketType := 0;
FFWSGetSocketOption(aSocket, SOL_SOCKET, SO_TYPE, SocketType,
sizeof(SocketType));
if (SocketType = SOCK_STREAM) then begin
Conn := wspGetConnForSocket(aSocket);
if Assigned(Conn) then begin
Conn.wscIsSending := False;
while (Not Conn.wscIsSending) and Assigned(Conn.wscPacketHead) do
{try to send all outstanding packets}
Conn.Send(nil, 0, 0, dummy, True); {!!.06}
end;
end;
end;
{--------}
procedure TffWinsockProtocol.wspReceiveCompleted(aSocket : TffwsSocket);
var
SocketType : integer;
begin
SocketType := 0;
FFWSGetSocketOption(aSocket, SOL_SOCKET, SO_TYPE, SocketType, sizeof(SocketType));
if (SocketType = SOCK_STREAM) then
wspReceiveMsgCompleted(aSocket)
else if (SocketType = SOCK_DGRAM) then
wspReceiveDatagramCompleted(aSocket);
end;
{--------}
procedure TffWinsockProtocol.wspReceiveDatagramCompleted(aSocket : TffwsSocket);
var
RemNetName : TffNetName;
BytesAvail : longint;
BytesRead : integer;
Error : integer;
SockAddrLen: integer;
SockAddr : TffwsSockAddr;
begin
Error := WinsockRoutines.ioctlsocket(aSocket, FIONREAD, BytesAvail);
if (Error <> SOCKET_ERROR) and (BytesAvail > 0) then begin
FillChar(SockAddr, sizeof(SockAddr), 0);
if (Family = wfTCP) then begin
SockAddrLen := sizeof(TffwsSockAddrIn);
end
else {Family <> wfTCP} begin
SockAddrLen := sizeof(TffwsSockAddrIPX);
end;
BytesRead := WinsockRoutines.recvfrom(aSocket,
wspRcvDGBuffer^,
ffc_MaxDatagramSize,
0,
SockAddr,
SockAddrLen);
if (BytesRead <> SOCKET_ERROR) then begin
{get our user to process the data}
if (Family = wfTCP) then begin
RemNetName := FFWSCvtAddrToStr(SockAddr.TCP.sin_addr);
end
else {Family <> wfTCP} begin
with SockAddr.IPX do
RemNetName :=
FFWSCvtIPXAddrToStr(sipx_netnum, sipx_nodenum);
end;
cpDoReceiveDatagram(RemNetName, wspRcvDGBuffer, BytesRead);
end;
end;
end;
{--------}
procedure TffWinsockProtocol.wspReceiveMsgCompleted(aSocket : TffwsSocket);
var
BytesAvail : longint;
BytesRead : integer;
Conn : TffWinsockConnection;
Error : integer;
MsgLen : integer;
Parsing : boolean;
begin
Error := WinsockRoutines.ioctlsocket(aSocket, FIONREAD, BytesAvail);
if (Error <> SOCKET_ERROR) and (BytesAvail > 0) then begin
Conn := wspGetConnForSocket(aSocket);
if assigned(Conn) then
with Conn do begin
{read everything we can}
BytesRead := WinsockRoutines.recv(aSocket,
RcvBuffer^[RcvBufferOffset],
ffc_MaxWinsockMsgSize - RcvBufferOffset,
0);
if (BytesRead <> SOCKET_ERROR) then begin
{calculate the number of valid bytes in our receive buffer}
RcvBufferOffset := RcvBufferOffset + BytesRead;
Parsing := true;
while Parsing do begin
Parsing := false;
{discard check connection (keepalive) messages now, we may
have real messages piggybacking one}
while (RcvBufferOffset >= sizeof(longint)) and
(PLongint(RcvBuffer)^ = ffnmCheckConnection) do begin
{move the remainder of the received data up by 4 bytes}
RcvBufferOffset := RcvBufferOffset - sizeof(longint);
if (RcvBufferOffset > 0) then
Move(RcvBuffer^[sizeof(longint)], RcvBuffer^[0], RcvBufferOffset);
cpGotCheckConnection(Conn);
Parsing := true;
end; { while }
{if we have something left..., and enough of it...}
if (RcvBufferOffset >= ffc_NetMsgHeaderSize) then begin
MsgLen := PffnmHeader(RcvBuffer)^.nmhMsgLen;
if (RcvBufferOffset >= MsgLen) then begin
{get our ancestor to process the data}
if cpDoReceiveMsg(Conn, RcvBuffer, MsgLen) then begin
{remove the message}
RcvBufferOffset := RcvBufferOffset - MsgLen;
if (RcvBufferOffset > 0) then
Move(RcvBuffer^[MsgLen], RcvBuffer^[0], RcvBufferOffset);
Parsing := true;
end;
end;
end; { if }
end; { while }
end; { if }
end { with }
else
LogStrFmt('Could not find connection for socket %d', [aSocket]);
end; { if }
end;
{--------}
procedure TffWinsockProtocol.wspWaitForConnect(aTimeOut : integer);
var
T : TffTimer;
begin
SetTimer(T, aTimeOut);
while wspWaitingForConnect and (not HasTimerExpired(T)) do begin
Breathe;
end;
end;
{--------}
function TffWinsockProtocol.wspWaitForSendToUnblock : Boolean;
{ Rewritten !!.06}
var
UnblockTimer : TffTimer;
begin
wspWaitingForSendToUnblock := true;
SetTimer(UnblockTimer, ffc_UnblockWait);
repeat
Breathe;
Result := HasTimerExpired(UnblockTimer);
until (not wspWaitingForSendToUnblock) or Result;
end;
{--------}
procedure TffWinsockProtocol.wspWSAEventCompleted(var WSMsg : TMessage);
begin
with WSMsg do begin
if (Msg = ffwscEventComplete) then begin
wspProcessCompletedWSACall(WParam, LParam);
Result := 0;
end
else if (Msg = WM_TIMER) then begin
cpTimerTick;
end
else
Result := DefWindowProc(FNotifyWindow, Msg, WParam, LParam);
end;
end;
{====================================================================}
{===TffTCPIPProtocol=================================================}
constructor TffTCPIPProtocol.Create(const aName : TffNetAddress;
aCSType : TffClientServerType);
begin
inherited Create(aName, aCSType);
Family := wfTCP;
end;
{--------}
class function TffTCPIPProtocol.GetProtocolName : string;
begin
Result := 'TCP/IP (FF)';
end;
{--------}
class function TffTCPIPProtocol.Supported : boolean;
begin
if FFWSInstalled then
Result := wfTCP in ffwsFamiliesInstalled
else
Result := False;
end;
{====================================================================}
{===TffIPXSPXProtocol================================================}
constructor TffIPXSPXProtocol.Create(const aName : TffNetAddress;
aCSType : TffClientServerType);
begin
inherited Create(aName, aCSType);
Family := wfIPX;
end;
{--------}
class function TffIPXSPXProtocol.GetProtocolName : string;
begin
Result := 'IPX/SPX (FF)';
end;
{--------}
class function TffIPXSPXProtocol.Supported : boolean;
begin
if FFWSInstalled then
Result := wfIPX in ffwsFamiliesInstalled
else
Result := False;
end;
{====================================================================}
{===Helper routines for single user==================================}
type
PffSUEnumData = ^TffSUEnumData;
TffSUEnumData = packed record
MsgID : integer;
OurWnd : HWND;
SrvWnd : HWND;
end;
{====================================================================}
{===TffSingleUserConnection==========================================}
constructor TffSingleUserConnection.Create(aOwner : TffBaseCommsProtocol;
aRemoteName : TffNetAddress;
aUs : HWND;
aPartner : HWND);
begin
inherited Create(aOwner, aRemoteName);
FUs := aUs;
FPartner := aPartner;
GetMem(sucSendBuffer, ffc_MaxSingleUserMsgSize);
end;
{--------}
destructor TffSingleUserConnection.Destroy;
var
CDS : TCopyDataStruct;
MsgResult : DWORD;
WinError : TffWord32; {!!.12}
begin
{ If we are deliberately hanging up then send a message to our partner. }
if FHangingUp then begin
if IsWindow(Partner) then begin
CDS.dwData := ffsumHangUp;
CDS.cbData := 0;
CDS.lpData := nil;
{Begin !!.12}
if not LongBool(SendMessageTimeout(FPartner, WM_COPYDATA, FClientID,
longint(@CDS),
{$IFDEF RunningUnitTests}
SMTO_ABORTIFHUNG,
{$ELSE}
SMTO_ABORTIFHUNG or SMTO_BLOCK,
{$ENDIF}
ffc_SendMessageTimeout, MsgResult)) or
(MsgResult <> 0) then begin
Sleep(ffc_SUPErrorTimeout);
{ Experimentation shows the following:
1. The first SendMessageTimeout will return False but
GetLastError returns zero.
2. Leaving out the Sleep() leads to a failure in the following
call to SendMessageTimeout. Note that error code is still
set to zero in that case.
3. Inserting a Sleep(1) resolves one timeout scenario (loading
JPEGs from table). However, it does not resolve the issue
where Keep Alive Interval >= 20000 and scrolling through
large table in FFE.
4. Inserting a Sleep(25) resolves the latter case mentioned in
Item 3. }
if not LongBool(SendMessageTimeout(FPartner, WM_COPYDATA, FClientID,
longint(@CDS),
{$IFDEF RunningUnitTests}
SMTO_ABORTIFHUNG,
{$ELSE}
SMTO_ABORTIFHUNG or SMTO_BLOCK,
{$ENDIF}
ffc_SendMessageTimeout, MsgResult)) then begin
WinError := GetLastError;
FOwner.LogStrFmt('Error %d sending message via SUP connection: %s',
[WinError, SysErrorMessage(WinError)]);
end;
end;
{End !!.12}
end;
end;
FreeMem(sucSendBuffer, ffc_MaxSingleUserMsgSize);
inherited Destroy;
end;
{--------}
Procedure TffSingleUserConnection.AddToList(List : TFFList);
var
T : TffIntListItem;
{$IFNDEF WIN32}
tmpLongInt : longInt;
{$ENDIF}
begin {add a list entry to allow partner hwnd lookups}
{$IFDEF WIN32}
T := TffIntListItem.Create(FPartner);
{$ELSE}
{ The 16-bit HWND is a Word. Cast it to a longInt so that
our TffIntList comparison will work. }
tmpLongInt := FPartner;
T := TffIntListItem.Create(tmpLongInt);
{$ENDIF}
T.ExtraData := Self;
List.Insert(T);
end;
{--------}
class function TffSingleUserProtocol.GetProtocolName : string;
begin
Result := 'Single User (FF)';
end;
{--------}
Procedure TffSingleUserConnection.RemoveFromList(List : TFFList);
begin
List.Delete(FPartner);
end;
{--------}
procedure TffSingleUserConnection.Send(aData : PffByteArray;
aDataLen : longint;
aConnLock : Boolean); {!!.06}
var
CDS : TCopyDataStruct;
MsgResult : DWORD;
WinError : TffWord32; {!!.05}
begin
if IsWindow(Partner) then begin
if aConnLock then {!!.06}
HangupLock; {!!.05}
try {!!.05}
if (aDataLen <> 0) then begin
Move(aData^, sucSendBuffer^, aDataLen);
Owner.cpCodeMessage(Self, sucSendBuffer, aDataLen);
CDS.lpData := sucSendBuffer;
CDS.cbData := aDataLen;
end
else begin
CDS.lpData := nil;
CDS.cbData := 0;
end;
CDS.dwData := ffsumDataMsg;
{Begin !!.05}
if not LongBool(SendMessageTimeout(FPartner, WM_COPYDATA, FClientID,
longint(@CDS),
{$IFDEF RunningUnitTests}
SMTO_ABORTIFHUNG,
{$ELSE}
SMTO_ABORTIFHUNG or SMTO_BLOCK,
{$ENDIF}
ffc_SendMessageTimeout, MsgResult)) or
(MsgResult <> 0) then begin
{Begin !!.06}
Sleep(ffc_SUPErrorTimeout);
{ Experimentation shows the following:
1. The first SendMessageTimeout will return False but
GetLastError returns zero.
2. Leaving out the Sleep() leads to a failure in the following
call to SendMessageTimeout. Note that error code is still
set to zero in that case.
3. Inserting a Sleep(1) resolves one timeout scenario (loading
JPEGs from table). However, it does not resolve the issue
where Keep Alive Interval >= 20000 and scrolling through
large table in FFE.
4. Inserting a Sleep(25) resolves the latter case mentioned in
Item 3. }
{End !!.06}
if not LongBool(SendMessageTimeout(FPartner, WM_COPYDATA, FClientID,
longint(@CDS),
{$IFDEF RunningUnitTests}
SMTO_ABORTIFHUNG,
{$ELSE}
SMTO_ABORTIFHUNG or SMTO_BLOCK,
{$ENDIF}
ffc_SendMessageTimeout, MsgResult)) then begin
WinError := GetLastError;
FOwner.LogStrFmt('Error %d sending message via SUP connection: %s',
[WinError, SysErrorMessage(WinError)]);
end;
{End !!.05}
end;
finally {!!.05}
if aConnLock then {!!.06}
HangupUnlock; {!!.05}
end; {!!.05}
end;
end;
{====================================================================}
{===TffSingleUserProtocol============================================}
constructor TffSingleUserProtocol.Create(const aName : TffNetAddress;
aCSType : TffClientServerType);
begin
inherited Create(aName, aCSType);
FMaxNetMsgSize := ffc_MaxSingleUserMsgSize;
{ Create a new Windows message. }
supMsgID := RegisterWindowMessage('FlashFiler2SingleUser');
supPostMsgID := RegisterWindowMessage('FlashFiler2SingleUserPostMessage');
end;
{--------}
function TffSingleUserProtocol.Call(const aServerName : TffNetName;
var aClientID : TffClientID;
const timeout : longInt) : TffResult;
var
Conn : TffSingleUserConnection;
SUED : TffSUEnumData;
begin
Result := DBIERR_NONE;
{servers don't call}
if (CSType = csServer) then
raise EffCommsException.CreateNoData(ffStrResGeneral, fferrCommsCannotCall);
{assume failure}
{enumerate the top-level windows, looking for a server}
SUED.MsgID := supMsgID;
SUED.OurWnd := FNotifyWindow;
SUED.SrvWnd := 0;
{ Create a connection object with the assumption we find a server. }
Conn := TffSingleUserConnection.Create(Self, '', FNotifyWindow, SUED.SrvWnd);
Conn.ClientID := Conn.Handle;
SUED.SrvWnd := supFindPartner(Conn.ClientID, timeout);
{did we find one?}
if (SUED.SrvWnd <> 0) then begin
Conn.Partner := SUED.SrvWnd;
cpAddConnection(Conn);
aClientID := Conn.ClientID;
end else begin
Conn.Free;
Result := DBIERR_SERVERNOTFOUND;
end;
end;
{--------}
procedure TffSingleUserProtocol.cpPerformStartUp;
begin
{create our Window}
if not cpCreateNotifyWindow then begin
LogStr('Could not create notification window.');
raise EffCommsException.CreateNoData(ffStrResGeneral, fferrCommsNoWinRes);
end;
end;
{--------}
procedure TffSingleUserProtocol.GetServerNames(aList : TStrings; const timeout : longInt);
begin
if not assigned(aList) then
exit;
aList.Clear;
aList.Add(ffc_SingleUserServerName);
end;
{--------}
procedure TffSingleUserProtocol.HangUp(aConn : TffConnection);
begin
cpDoHangUp(aConn);
cpRemoveConnection(aConn.ClientID);
end;
{--------}
procedure TffSingleUserProtocol.Listen;
begin
end;
{--------}
procedure TffSingleUserProtocol.ReceiveDatagram;
begin
if not supReceivingDatagram then
supReceivingDatagram := true;
end;
{--------}
procedure TffSingleUserProtocol.SendDatagram(const aName : TffNetName;
aData : PffByteArray;
aDataLen : longint);
begin
end;
{--------}
function TffSingleUserProtocol.SendMsg(aClientID : TffClientID;
aData : PffByteArray;
aDataLen : longint;
aConnLock : Boolean) : TffResult; {!!.06}
var
Conn : TffSingleUserConnection;
begin
Result := DBIERR_NONE;
Conn := TffSingleUserConnection(cpGetConnection(aClientID));
if Assigned(Conn) then
Conn.Send(aData, aDataLen, aConnLock) {!!.06}
else
Result := fferrConnectionLost;
end;
{--------}
procedure TffSingleUserProtocol.StopReceiveDatagram;
begin
if supReceivingDatagram then
supReceivingDatagram := false;
end;
{--------}
function TffSingleUserProtocol.cpCreateNotifyWindow : boolean;
begin
{$IFDEF DCC6OrLater} {!!.11}
{$WARN SYMBOL_DEPRECATED OFF}
{$ENDIF}
{$ifdef fpc}
FNotifyWindow := LCLIntf.AllocateHWnd(supMsgReceived); //soner
{$else}
FNotifyWindow := AllocateHWnd(supMsgReceived);
{$endif}
{$IFDEF DCC6OrLater} {!!.11}
{$WARN SYMBOL_DEPRECATED ON}
{$ENDIF}
Result := FNotifyWindow <> 0;
if Result then begin
{$IFDEF KALog}
KALog.WriteStringFmt('SingleUser.cpCreateNotifyWindow: protocol %d',
[Longint(Self)]);
{$ENDIF}
Windows.SetTimer(FNotifyWindow, 1, FKeepAliveInterval, nil); {!!.05}
end;
end;
{--------}
procedure TffSingleUserProtocol.supDataMsgReceived(const aClientID : TffClientID;
const aCDS : TCopyDataStruct);
var
Conn : TffSingleUserConnection;
begin
Conn := TffSingleUserConnection(cpGetConnection(aClientID));
{get our user to process the data}
if assigned(Conn) then
cpDoReceiveMsg(Conn, aCDS.lpData, aCDS.cbData)
else
LogStrFmt('Could not find connection for client %d', [aClientID]);
end;
{--------}
function TffSingleUserProtocol.supGetConnForPartner(aPartner : HWND) : TffSingleUserConnection;
var
Inx : integer;
T : TffIntListItem;
begin
{ If we are indexing connections then use the index to locate
the connection. }
if Assigned(cpIndexByOSConnector) then begin
T := TffIntListItem(cpIndexByOSConnector.Items[cpIndexByOSConnector.Index(aPartner)]);
if T = Nil then
Result := Nil
else
Result := T.ExtraData;
exit;
end;
for Inx := 0 to pred(cpConnList.Count) do begin
Result := TffSingleUserConnection(cpConnList[Inx]);
if (Result.Partner = aPartner) then
Exit;
end;
Result := nil;
end;
{--------}
procedure TffSingleUserProtocol.supHangupDetected(const aClientID : TffClientID);
{Rewritten !!.06}
var
Conn : TffSingleUserConnection;
begin
Conn := TffsingleUserConnection(cpGetConnection(aClientID));
if Conn <> nil then begin
Conn.HangingUp := False;
HangUp(Conn);
end;
end;
{--------}
procedure TffSingleUserProtocol.supListenCompleted(aClientID : TffClientID;
Wnd : HWND);
var
Conn : TffSingleUserConnection;
WasAdded : boolean;
begin
{a listen event has been accepted, create a connection}
WasAdded := false;
Conn := nil;
try
{ When we first create this connection, we don't have a clientID so
we temporarily use the connection's handle. There is also a temporary
clientID on the client-side of things.
When the client is given a real clientID, the temporary clientIDs on
both client and server are replaced with the true clientID. }
Conn := TffSingleUserConnection.Create(Self, '', FNotifyWindow, Wnd);
Conn.ClientID := aClientID;
// Conn.InitCode(0); {Deleted !!.05}
cpAddConnection(Conn);
WasAdded := True;
cpDoHeardCall(Conn.ClientID);
except
if WasAdded then
cpRemoveConnection(Conn.ClientID);
raise;
end;{try..except}
end;
{--------}
procedure TffSingleUserProtocol.supMsgReceived(var SUMsg : TMessage);
begin
with SUMsg do begin
if (Msg = supMsgID) then begin
if (CSType = csServer) then begin
Result := ffsumCallServer {'FF'};
supListenCompleted(WParam, LParam);
end
else
Result := 0;
end
else if Msg = supPostMsgID then begin
if CSType = csServer then begin
{ Client is trying to initiate conversation with us. Send back
a reply. }
if LParam = ffsumCallServer {'FF'} then begin
if IsWindow(WParam) then
PostMessage(WParam, ffm_ServerReply, FNotifyWindow, ffsumCallServer);
end;
end;
end
else if Msg = ffm_ServerReply then begin
if supPartner = 0 then begin
if CSType = csClient then begin
if LParam = ffsumCallServer {'FF'} then begin
if IsWindow(WParam) then
supPartner := WParam;
end;
end;
end;
end
else if (Msg = WM_COPYDATA) then begin
case PCopyDataStruct(LParam)^.dwData of
ffsumDataMsg : supDataMsgReceived(WParam, PCopyDataStruct(LParam)^);
ffsumHangUp : supHangUpDetected(WParam);
end;
end
else if (Msg = WM_TIMER) then
cpTimerTick
else
Result := DefWindowProc(FNotifyWindow, Msg, WParam, LParam);
end;
end;
{--------}
function TffSingleUserProtocol.supFindPartner(const aClientID : TffClientID;
const timeout : longInt): HWND;
var
WaitUntil : Tffword32;
MsgResult : DWORD;
Msg : TMsg;
StartTime : DWORD; {!!.05}
WinError : TffWord32; {!!.05}
begin
supPartner:=0;
PostMessage(HWND_BROADCAST, supPostMsgID, FNotifyWindow, ffsumCallServer);
WaitUntil := GetTickCount + DWORD(timeout);
StartTime := GetTickCount; {!!.05}
while (GetTickCount < WaitUntil) and (supPartner=0) do begin
if PeekMessage(Msg, FNotifyWindow, ffm_ServerReply,
ffm_ServerReply, PM_REMOVE) then begin
TranslateMessage(Msg);
DispatchMessage(Msg);
{Begin !!.05}
end
else if GetTickCount - StartTime > ffc_ConnectRetryTimeout then begin
PostMessage(HWND_BROADCAST, supPostMsgID, FNotifyWindow, ffsumCallServer);
StartTime := GetTickCount;
end;
{End !!.05}
if supPartner = 0 then
Breathe;
end;
Result := supPartner;
if Result <> 0 then begin
if LongBool(SendMessageTimeout(Result, supMsgID, aClientID, FNotifyWindow,
SMTO_ABORTIFHUNG or SMTO_BLOCK,
timeout, MsgResult)) then begin
if MsgResult <> ffsumCallServer{FF} then begin
{Begin !!.05}
if LongBool(SendMessageTimeout(Result, supMsgID, aClientID, FNotifyWindow,
SMTO_ABORTIFHUNG or SMTO_BLOCK,
timeout, MsgResult)) then
if MsgResult <> ffsumCallServer{FF} then begin
WinError := GetLastError;
LogStrFmt('Error %d when finding SUP partner: %s',
[WinError, SysErrorMessage(WinError)]);
Result :=0;
end; { if }
end; { if }
{End !!.05}
end
else
Result := 0;
end;
end;
{====================================================================}
{$IFDEF KALog}
initialization
KALog := TffEventLog.Create(nil);
KALog.FileName := ChangeFileExt(ParamStr(0), '') + 'KA.log';
KALog.Enabled := True;
finalization
KALog.Free;
{$ENDIF}
end.