IdHL7.pas 58 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919
  1. {
  2. $Project$
  3. $Workfile$
  4. $Revision$
  5. $DateUTC$
  6. $Id$
  7. This file is part of the Indy (Internet Direct) project, and is offered
  8. under the dual-licensing agreement described on the Indy website.
  9. (http://www.indyproject.org/)
  10. Copyright:
  11. (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
  12. }
  13. {
  14. $Log$
  15. }
  16. {
  17. Rev 2.2 30/12/2022 5:40 PM RLebeau
  18. Code Review:
  19. Proper handling of TIdQueuedMessage as an interfaced object (removing manual reference counting).
  20. Handling of incoming messages more efficiently (not reading byte by byte, not treating message delimiters as encoded strings).
  21. Updating String encoding to support non-Unicode compilers.
  22. General code cleanup
  23. Rev 2.1 25/12/2022 12:32 AM EJPretorius/ ShoraiTek
  24. Added DefStringEncoding for sending encoding control / default to utf8
  25. Use dateutils functions rather where possible
  26. remove TIdPeerThread classes logic us TIdContext rather directly
  27. use indy KeepAlive functions (timeouts to be set)
  28. use global.ticks rather
  29. non-type casting of socket - use direct property rather
  30. use IPv4ToUInt32 rather than custom function
  31. reverse Cardinal change back to longword
  32. readbyte ansichar cast changed to char cast for charsets
  33. Rev 2.0 23/12/2022 19:52 PM EJPretorius
  34. Combined Indy code with last source code release by original author (Grahame Grieve) in 2013
  35. http://www.healthintersections.com.au/?p=1596
  36. Rev 1.9 9/30/2004 5:04:18 PM BGooijen
  37. Self was not initialized
  38. Rev 1.8 6/11/2004 9:36:14 AM DSiders
  39. Added "Do not Localize" comments.
  40. Rev 1.7 2004.02.07 5:03:02 PM czhower
  41. .net fixes.
  42. Rev 1.6 2004.02.03 5:43:44 PM czhower
  43. Name changes
  44. Rev 1.5 1/21/2004 2:42:46 PM JPMugaas
  45. InitComponent
  46. Rev 1.4 1/3/2004 12:59:54 PM JPMugaas
  47. These should now compile with Kudzu's change in IdCoreGlobal.
  48. Rev 1.3 4/12/2003 9:21:32 PM GGrieve
  49. give up on Indy10 for the moment
  50. Rev 1.2 10/15/2003 9:53:42 PM GGrieve
  51. DotNet changes
  52. Rev 1.1 23/6/2003 22:33:54 GGrieve
  53. update for indy10 IOHandler model
  54. Rev 1.0 11/13/2002 07:53:58 AM JPMugaas
  55. 05/09/2002 Grahame Grieve
  56. Fixed SingleThread Timeout Issues + WaitForConnection
  57. 23/01/2002 Grahame Grieve
  58. Fixed for network changes to TIdTCPxxx wrote DUnit testing,
  59. increased assertions change OnMessageReceive,
  60. added VHandled parameter
  61. 07/12/2001 Grahame Grieve Various fixes for cmSingleThread mode
  62. 05/11/2001 Grahame Grieve Merge into Indy
  63. 03/09/2001 Grahame Grieve Prepare for Indy
  64. }
  65. {
  66. Indy HL7 Minimal Lower Layer Protocol TIdHL7
  67. Original author Grahame Grieve
  68. This code was donated by HL7Connect.com
  69. For more HL7 open source code see
  70. http://www.hl7connect.com/tools
  71. This unit implements support for the Standard HL7 minimal Lower Layer
  72. protocol. For further details, consult the HL7 standard (www.hl7.org).
  73. Before you can use this component, you must set the following properties:
  74. CommunicationMode
  75. Address (if you want to be a client)
  76. Port
  77. isListener
  78. and hook the appropriate events (see below)
  79. This component will operate as either a server or a client depending on
  80. the configuration
  81. }
  82. (*
  83. note: Events are structurally important for this component. However there is
  84. a bug in SyncObjs for Linux under Kylix 1 and 2 where TEvent.WaitFor cannot be
  85. used with timeouts. If you compile your own RTL, then you can fix the routine
  86. like this:
  87. function TEvent.WaitFor(Timeout: LongWord): TWaitResult;
  88. {$IFDEF LINUX}
  89. var ts : TTimeSpec;
  90. begin
  91. ts.tv_sec := timeout div 1000;
  92. ts.tv_nsec := (timeout mod 1000) * 1000000;
  93. if sem_timedwait(FSem, ts) = 0 then
  94. result := wrSignaled
  95. else
  96. result := wrTimeOut;
  97. {$ENDIF}
  98. and then disable this define:
  99. this is a serious issue - unless you fix the RTL, this component does not
  100. function properly on Linux at the present time. This may be fixed in a
  101. future version
  102. *)
  103. { TODO : use Server.MaxConnections }
  104. unit IdHL7;
  105. interface
  106. {$i IdCompilerDefines.inc}
  107. uses
  108. Classes,
  109. Contnrs,
  110. IdBaseComponent,
  111. IdContext,
  112. IdException,
  113. IdGlobal,
  114. IdTCPClient,
  115. IdTCPConnection,
  116. IdTCPServer,
  117. SysUtils;
  118. const
  119. MSG_START: array[0..0] of Byte = ($0B);
  120. MSG_END: array[0..1] of Byte = ($1C, $0D);
  121. BUFFER_SIZE_LIMIT = $FFFFFFF; // buffer is allowed to grow to this size without any valid messages. Will be truncated with no notice (DoS protection) (268MB)
  122. WAIT_STOP = 5000; // how long we wait for things to shut down cleanly
  123. type
  124. EHL7CommunicationError = class(EIdException)
  125. protected
  126. FInterfaceName: String;
  127. public
  128. constructor Create(AnInterfaceName, AMessage: String);
  129. property InterfaceName: String read FInterfaceName;
  130. end;
  131. THL7CommunicationMode = (cmUnknown, // not valid - default setting must be changed by application
  132. cmAsynchronous, // see comments below for meanings of the other parameters
  133. cmSynchronous,
  134. cmSingleThread);
  135. TSendResponse = (srNone, // internal use only - never returned
  136. srError, // internal use only - never returned
  137. srNoConnection, // you tried to send but there was no connection
  138. srSent, // you asked to send without waiting, and it has been done
  139. srOK, // sent ok, and response returned
  140. srTimeout); // we sent but there was no response (connection will be dropped internally
  141. TIdHL7Status = (isStopped, // not doing anything
  142. isNotConnected, // not Connected (Server state)
  143. isConnecting, // Client is attempting to connect
  144. isWaitReconnect, // Client is in delay loop prior to attempting to connect
  145. isConnected, // connected OK
  146. isUnusable, // Not Usable - stop failed
  147. isTimedOut // we are a client, and there was no traffic, so we closed the connection (and we are not listening)
  148. );
  149. const
  150. { default property values }
  151. DEFAULT_ADDRESS = ''; {do not localize}
  152. DEFAULT_PORT = 0;
  153. DEFAULT_TIMEOUT = 30000;
  154. DEFAULT_RECEIVE_TIMEOUT = 30000;
  155. NULL_IP = '0.0.0.0'; {do not localize}
  156. DEFAULT_CONN_LIMIT = 1;
  157. DEFAULT_RECONNECT_DELAY = 15000;
  158. DEFAULT_CONNECTION_TIMEOUT = 0;
  159. DEFAULT_COMM_MODE = cmUnknown;
  160. DEFAULT_IS_LISTENER = True;
  161. SEND_RESPONSE_NAMES: array[TSendResponse] of String = ('None', 'Error', 'NoConnection', 'Sent', 'OK', 'Timeout'); {Do not Localize}
  162. type
  163. // the connection is provided in these events so that applications can obtain information about the
  164. // the peer. It's never OK to write to these connections
  165. TMessageArriveEvent = procedure(ASender: TObject; AConnection: TIdTCPConnection; AMsg: String) of object;
  166. TMessageReceiveEvent = procedure(ASender: TObject; AConnection: TIdTCPConnection; AMsg: String; var VHandled: Boolean; var VReply: String) of object;
  167. TReceiveErrorEvent = procedure(ASender: TObject; AConnection: TIdTCPConnection; AMsg: String; AException: Exception; var VReply: String; var VDropConnection: Boolean) of object;
  168. TIdHL7 = class;
  169. TIdHL7ConnCountEvent = procedure(ASender: TIdHL7; AConnCount: integer) of object;
  170. { TIdHL7KeepAlive }
  171. TIdHL7KeepAlive = class(TPersistent)
  172. protected
  173. FUseKeepAlive: Boolean;
  174. FIdleTimeMS: Integer;
  175. FIntervalMS: Integer;
  176. public
  177. procedure Assign(Source: TPersistent); override;
  178. published
  179. property UseKeepAlive: Boolean read FUseKeepAlive write FUseKeepAlive;
  180. property IdleTimeMS: Integer read FIdleTimeMS write FIdleTimeMS;
  181. property IntervalMS: Integer read FIntervalMS write FIntervalMS;
  182. end;
  183. TIdHL7ClientThread = class(TThread)
  184. protected
  185. FClient: TIdTCPClient;
  186. FCloseEvent: TIdLocalEvent;
  187. FOwner: TIdHL7;
  188. FLastTraffic: TIdTicks;
  189. procedure Execute; override;
  190. procedure PollStack;
  191. function TimedOut: Boolean;
  192. public
  193. constructor Create(AOwner: TIdHL7);
  194. destructor Destroy; override;
  195. end;
  196. TIdHL7 = class(TIdBaseComponent)
  197. protected
  198. FLock: TIdCriticalSection;
  199. FStatus: TIdHL7Status;
  200. FStatusDesc: String;
  201. // these queues hold messages when running in singlethread mode
  202. FMsgQueue: TInterfaceList;
  203. FHndMsgQueue: TInterfaceList;
  204. FAddress: String;
  205. FCommunicationMode: THL7CommunicationMode;
  206. FConnectionLimit: Word;
  207. FIPMask: String;
  208. FIPRestriction: String;
  209. FIPMaskVal: UInt32;
  210. FIPRestrictionVal: UInt32;
  211. FIsListener: Boolean;
  212. FObject: TObject;
  213. FPreStopped: Boolean;
  214. FPort: Word;
  215. FReconnectDelay: LongWord;
  216. FTimeOut: UInt32;
  217. FReceiveTimeout: LongWord;
  218. FServerConnections: TObjectList;
  219. FOnConnect: TNotifyEvent;
  220. FOnDisconnect: TNotifyEvent;
  221. FOnConnCountChange: TIdHL7ConnCountEvent;
  222. FOnMessageArrive: TMessageArriveEvent;
  223. FOnReceiveMessage: TMessageReceiveEvent;
  224. FOnReceiveError: TReceiveErrorEvent;
  225. FIsServer: Boolean;
  226. FServer: TIdTCPServer;
  227. // if we are a server, and the mode is not asynchronous, and we are not listening, then
  228. // we will track the current server connection with this, so we can initiate sending on it
  229. FServerConn: TIdTCPConnection;
  230. FIsServerExecuting: Boolean;
  231. // A thread exists to connect and receive incoming tcp traffic
  232. FClientThread: TIdHL7ClientThread;
  233. FClient: TIdTCPClient;
  234. // these fields are used for handling message response in synchronous mode
  235. FWaitingForAnswer: Boolean;
  236. FWaitStart: TIdTicks;
  237. FMsgReply: String;
  238. FReplyResponse: TSendResponse;
  239. FWaitEvent: TIdLocalEvent;
  240. FKeepAlive: TIdHL7KeepAlive;
  241. FConnectionTimeout: UInt32;
  242. FDefStringEncoding: IIdTextEncoding;
  243. procedure SetAddress(const AValue: String);
  244. procedure SetKeepAlive(const AValue: TIdHL7KeepAlive);
  245. procedure SetConnectionLimit(const AValue: Word);
  246. procedure SetIPMask(const AValue: String);
  247. procedure SetIPRestriction(const AValue: String);
  248. procedure SetPort(const AValue: Word);
  249. procedure SetReconnectDelay(const AValue: LongWord);
  250. procedure SetConnectionTimeout(const AValue: UInt32);
  251. procedure SetTimeOut(const AValue: UInt32);
  252. procedure SetCommunicationMode(const AValue: THL7CommunicationMode);
  253. procedure SetIsListener(const AValue: Boolean);
  254. procedure SetDefStringEncoding(const AValue: IIdTextEncoding);
  255. function GetStatus: TIdHL7Status;
  256. function GetStatusDesc: String;
  257. procedure InternalSetStatus(const AStatus: TIdHL7Status; ADesc: String);
  258. procedure CheckServerParameters;
  259. procedure StartServer;
  260. procedure StopServer;
  261. procedure DropServerConnection;
  262. procedure ServerConnect(AContext: TIdContext);
  263. procedure ServerExecute(AContext: TIdContext);
  264. procedure ServerDisconnect(AContext: TIdContext);
  265. procedure CheckClientParameters;
  266. procedure StartClient;
  267. procedure StopClient;
  268. procedure DropClientConnection;
  269. procedure ReConnectFromTimeout;
  270. procedure HandleIncoming(var VBuffer: TIdBytes; AConnection: TIdTCPConnection);
  271. function HandleMessage(const AMsg: String; AConn: TIdTCPConnection; var VReply: String): Boolean;
  272. public
  273. constructor Create(AOwner: TComponent); override;
  274. destructor Destroy; override;
  275. procedure EnforceWaitReplyTimeout;
  276. function Going: Boolean;
  277. // for the app to use to hold any related object
  278. property ObjTag: TObject read FObject write FObject;
  279. // status
  280. property Status: TIdHL7Status read GetStatus;
  281. property StatusDesc: String read GetStatusDesc;
  282. function Connected: Boolean;
  283. property IsServer: Boolean read FIsServer;
  284. procedure Start;
  285. procedure PreStop; // call this in advance to start the shut down process. You do not need to call this
  286. procedure Stop;
  287. procedure WaitForConnection(AMaxLength: UInt32); // milliseconds
  288. // asynchronous.
  289. function AsynchronousSend(const AMsg: String; ASyncConnection: TIdTCPConnection = nil): TSendResponse;
  290. property OnMessageArrive: TMessageArriveEvent read FOnMessageArrive write FOnMessageArrive;
  291. // synchronous
  292. function SynchronousSend(const AMsg: String; var VReply: String): TSendResponse;
  293. property OnReceiveMessage: TMessageReceiveEvent read FOnReceiveMessage write FOnReceiveMessage;
  294. procedure CheckSynchronousSendResult(AResult: TSendResponse; const AMsg: String);
  295. // single thread - like SynchronousSend, but don't hold the thread waiting
  296. procedure SendMessage(const AMsg: String);
  297. // you can't call SendMessage again without calling GetReply first
  298. function GetReply(var VReply: String): TSendResponse;
  299. function GetMessage(var VMsg: String): IInterface; // return nil if no messages
  300. // if you don't call SendReply then no reply will be sent.
  301. procedure SendReply(AMsgHnd: IInterface; const AReply: String);
  302. function HasClientConnection : Boolean;
  303. procedure Disconnect;
  304. property DefStringEncoding: IIdTextEncoding read FDefStringEncoding write SetDefStringEncoding;
  305. property IsServerExecuting: Boolean read FIsServerExecuting;
  306. published
  307. // basic properties
  308. property Address: String read FAddress write SetAddress; // leave blank and we will be server
  309. property Port: Word read FPort write SetPort default DEFAULT_PORT;
  310. property KeepAlive: TIdHL7KeepAlive read FKeepAlive write SetKeepAlive;
  311. // milliseconds - message timeout - how long we wait for other system to reply
  312. property TimeOut: UInt32 read FTimeOut write SetTimeOut default DEFAULT_TIMEOUT;
  313. // milliseconds - message timeout. When running cmSingleThread, how long we wait for the application to process an incoming message before giving up
  314. property ReceiveTimeout: LongWord read FReceiveTimeout write FReceiveTimeout default DEFAULT_RECEIVE_TIMEOUT;
  315. // server properties
  316. property ConnectionLimit: Word read FConnectionLimit write SetConnectionLimit default DEFAULT_CONN_LIMIT; // ignored if isListener is false
  317. property IPRestriction: String read FIPRestriction write SetIPRestriction;
  318. property IPMask: String read FIPMask write SetIPMask;
  319. // client properties
  320. // milliseconds - how long we wait after losing connection to retry
  321. property ReconnectDelay: LongWord read FReconnectDelay write SetReconnectDelay default DEFAULT_RECONNECT_DELAY;
  322. // milliseconds - how long we allow a connection to be open without traffic (damn firewalls)
  323. property ConnectionTimeout: UInt32 read FConnectionTimeout write SetConnectionTimeout default DEFAULT_CONNECTION_TIMEOUT;
  324. // message flow
  325. // Set this to one of 4 possibilities:
  326. //
  327. // cmUnknown
  328. // Default at start up. You must set a value before starting
  329. //
  330. // cmAsynchronous
  331. // Send Messages with AsynchronousSend. does not wait for
  332. // remote side to respond before returning
  333. // Receive Messages with OnMessageArrive. Message may
  334. // be response or new message
  335. // The application is responsible for responding to the remote
  336. // application and dropping the link as required
  337. // You must hook the OnMessageArrive Event before setting this mode
  338. // The property IsListener has no meaning in this mode
  339. //
  340. // cmSynchronous
  341. // Send Messages with SynchronousSend. Remote applications response
  342. // will be returned (or timeout). Only use if IsListener is false
  343. // Receive Messages with OnReceiveMessage. Only if IsListener is
  344. // true
  345. // In this mode, the object will wait for a response when sending,
  346. // and expects the application to reply when a message arrives.
  347. // In this mode, the interface can either be the listener or the
  348. // initiator but not both. IsListener controls which one.
  349. // note that OnReceiveMessage must be thread safe if you allow
  350. // more than one connection to a server
  351. //
  352. // cmSingleThread
  353. // Send Messages with SendMessage. Poll for answer using GetReply.
  354. // Only if isListener is false
  355. // Receive Messages using GetMessage. Return a response using
  356. // SendReply. Only if IsListener is true
  357. // This mode is the same as cmSynchronous, but the application is
  358. // assumed to be single threaded. The application must poll to
  359. // find out what is happening rather than being informed using
  360. // an event in a different thread
  361. property CommunicationMode: THL7CommunicationMode read FCommunicationMode write SetCommunicationMode default DEFAULT_COMM_MODE;
  362. // note that IsListener is not related to which end is client. Either end
  363. // may make the connection, and thereafter only one end will be the initiator
  364. // and one end will be the listener. Generally it is recommended that the
  365. // listener be the server. If the client is listening, network conditions
  366. // may lead to a state where the client has a phantom connection and it will
  367. // never find out since it doesn't initiate traffic. In this case, restart
  368. // the interface if there isn't traffic for a period
  369. property IsListener: Boolean read FIsListener write SetIsListener default DEFAULT_IS_LISTENER;
  370. // useful for application
  371. property OnConnect: TNotifyEvent read FOnConnect write FOnConnect;
  372. property OnDisconnect: TNotifyEvent read FOnDisconnect write FOnDisconnect;
  373. // this is called whenever OnConnect and OnDisconnect are called, and at other times, but only when server
  374. // it will be called after OnConnect and before OnDisconnect
  375. property OnConnCountChange: TIdHL7ConnCountEvent read FOnConnCountChange write FOnConnCountChange;
  376. // this is called when an unhandled exception is generated by the
  377. // hl7 object or the application. It allows the application to
  378. // construct a useful return error, log the exception, and drop the
  379. // connection if it wants
  380. property OnReceiveError: TReceiveErrorEvent read FOnReceiveError write FOnReceiveError;
  381. end;
  382. implementation
  383. uses
  384. {$IF DEFINED(USE_VCL_POSIX) AND DEFINED(OSX)}
  385. CoreServices,
  386. {$IFEND}
  387. IdGlobalProtocols,
  388. IdResourceStringsProtocols;
  389. type
  390. IIdQueuedMessage = interface(IInterface)
  391. ['{CF62BBC6-784E-4B79-B58B-4930330EB173}']
  392. function GetMessage: String;
  393. function GetReply: String;
  394. procedure SetReply(const AValue: String);
  395. procedure SetEvent;
  396. procedure Wait;
  397. //
  398. property Message: String read GetMessage;
  399. property Reply: String read GetReply write SetReply;
  400. end;
  401. TIdQueuedMessage = class(TInterfacedObject, IIdQueuedMessage)
  402. private
  403. FEvent: TIdLocalEvent;
  404. FMsg: String;
  405. FTimeOut: LongWord;
  406. FReply: String;
  407. public
  408. constructor Create(const AMsg: String; ATimeOut: LongWord);
  409. destructor Destroy; override;
  410. //
  411. function GetMessage: String;
  412. function GetReply: String;
  413. procedure SetReply(const AValue: String);
  414. procedure SetEvent;
  415. procedure Wait;
  416. end;
  417. { TIdHL7KeepAlive }
  418. procedure TIdHL7KeepAlive.Assign(Source: TPersistent);
  419. var
  420. LSource: TIdHL7KeepAlive;
  421. begin
  422. if Source is TIdHl7KeepAlive then begin
  423. LSource := TIdHL7KeepAlive(Source);
  424. FUseKeepAlive := LSource.UseKeepAlive;
  425. FIdleTimeMS := LSource.IdleTimeMS;
  426. FIntervalMS := LSource.IntervalMS;
  427. end else begin
  428. inherited Assign(Source);
  429. end;
  430. end;
  431. { TIdQueuedMessage }
  432. constructor TIdQueuedMessage.Create(const AMsg: String; ATimeOut: LongWord);
  433. begin
  434. Assert(Length(AMsg) > 0, 'Attempt to queue an empty message'); {do not localize}
  435. Assert(ATimeout <> 0, 'Attempt to queue a message with no timeout'); {do not localize}
  436. inherited Create;
  437. FEvent := TIdLocalEvent.Create(False, False);
  438. FMsg := AMsg;
  439. FTimeOut := ATimeOut;
  440. end;
  441. destructor TIdQueuedMessage.Destroy;
  442. begin
  443. Assert(Assigned(Self));
  444. FEvent.Free;
  445. inherited;
  446. end;
  447. function TIdQueuedMessage.GetMessage: String;
  448. begin
  449. Assert(Assigned(Self));
  450. Result := FMsg;
  451. end;
  452. function TIdQueuedMessage.GetReply: String;
  453. begin
  454. Assert(Assigned(Self));
  455. Result := FReply;
  456. end;
  457. procedure TIdQueuedMessage.SetReply(const AValue: String);
  458. begin
  459. Assert(Assigned(Self));
  460. FReply := AValue;
  461. end;
  462. procedure TIdQueuedMessage.SetEvent;
  463. begin
  464. Assert(Assigned(Self));
  465. Assert(Assigned(FEvent));
  466. FEvent.SetEvent;
  467. end;
  468. procedure TIdQueuedMessage.Wait;
  469. begin
  470. Assert(Assigned(Self));
  471. Assert(Assigned(FEvent));
  472. FEvent.WaitFor(FTimeOut);
  473. end;
  474. { EHL7CommunicationError }
  475. constructor EHL7CommunicationError.Create(AnInterfaceName, AMessage: String);
  476. begin
  477. //Assert(AInterfaceName <> '', 'Attempt to create an exception for an unnamed interface')
  478. //Assert(AMessage <> '', 'Attempt to create an exception with an empty message')
  479. // actually, we do not enforce either of these conditions, though they should both be true,
  480. // since we are already raising an exception
  481. FInterfaceName := AnInterfaceName;
  482. if FInterfaceName <> '' then
  483. begin
  484. inherited Create('[' + AnInterfaceName + '] ' + AMessage); {do not localize}
  485. end else begin
  486. inherited Create(AMessage);
  487. end;
  488. end;
  489. { TIdHL7 }
  490. constructor TIdHL7.Create(AOwner: TComponent);
  491. begin
  492. inherited Create(AOwner);
  493. // partly redundant initialization of properties
  494. FKeepAlive := TIdHL7KeepAlive.Create;
  495. FIsListener := DEFAULT_IS_LISTENER;
  496. FCommunicationMode := DEFAULT_COMM_MODE;
  497. FTimeOut := DEFAULT_TIMEOUT;
  498. FReconnectDelay := DEFAULT_RECONNECT_DELAY;
  499. FReceiveTimeout := DEFAULT_RECEIVE_TIMEOUT;
  500. FConnectionLimit := DEFAULT_CONN_LIMIT;
  501. FIPMask := NULL_IP;
  502. FIPRestriction := NULL_IP;
  503. FAddress := DEFAULT_ADDRESS;
  504. FPort := DEFAULT_PORT;
  505. FOnReceiveMessage := nil;
  506. FOnConnect := nil;
  507. FOnDisconnect := nil;
  508. FObject := nil;
  509. // initialise status
  510. FStatus := IsStopped;
  511. FStatusDesc := RSHL7StatusStopped;
  512. // build internal infrastructure
  513. FLock := TIdCriticalSection.Create;
  514. FServer := nil;
  515. FServerConn := nil;
  516. FClientThread := nil;
  517. FClient := nil;
  518. FMsgQueue := TInterfaceList.Create;
  519. FHndMsgQueue := TInterfaceList.Create;
  520. FWaitingForAnswer := False;
  521. FMsgReply := '';
  522. FReplyResponse := srNone;
  523. FWaitEvent := TIdLocalEvent.Create(False, False);
  524. FServerConnections := TObjectList.Create;
  525. FServerConnections.OwnsObjects := False;
  526. FDefStringEncoding := IndyTextEncoding_UTF8;
  527. end;
  528. destructor TIdHL7.Destroy;
  529. begin
  530. Assert(Assigned(Self));
  531. try
  532. if Going then
  533. begin
  534. Stop;
  535. end;
  536. finally
  537. FServerConnections.Free;
  538. FKeepAlive.Free;
  539. FMsgQueue.Free;
  540. FHndMsgQueue.Free;
  541. FWaitEvent.Free;
  542. FLock.Free;
  543. inherited;
  544. end;
  545. end;
  546. {==========================================================
  547. Property Servers
  548. ==========================================================}
  549. procedure TIdHL7.SetDefStringEncoding(const AValue: IIdTextEncoding);
  550. var
  551. LEncoding: IIdTextEncoding;
  552. begin
  553. Assert(Assigned(Self));
  554. if Going then
  555. begin
  556. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['DefStringEncoding'])); {do not localize}
  557. end;
  558. if FDefStringEncoding <> AValue then
  559. begin
  560. LEncoding := AValue;
  561. EnsureEncoding(LEncoding, encUTF8);
  562. FDefStringEncoding := LEncoding;
  563. end;
  564. end;
  565. procedure TIdHL7.SetAddress(const AValue: String);
  566. begin
  567. Assert(Assigned(Self));
  568. // we don't make any assertions about AValue - will be '' if we are a server
  569. if Going then
  570. begin
  571. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['Address'])); {do not localize??}
  572. end;
  573. FAddress := AValue;
  574. end;
  575. procedure TIdHL7.SetConnectionLimit(const AValue: Word);
  576. begin
  577. Assert(Assigned(Self));
  578. // no restrictions on AValue
  579. if Going then
  580. begin
  581. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['ConnectionLimit'])); {do not localize??}
  582. end;
  583. FConnectionLimit := AValue;
  584. end;
  585. procedure TIdHL7.SetIPMask(const AValue: String);
  586. begin
  587. Assert(Assigned(Self));
  588. // TODO: enforce that AValue is a valid Subnet mask
  589. if Going then
  590. begin
  591. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['IP Mask'])); {do not localize??}
  592. end;
  593. FIPMaskVal := IPv4ToUInt32(AValue);
  594. FIPMask := AValue;
  595. end;
  596. procedure TIdHL7.SetIPRestriction(const AValue: String);
  597. begin
  598. Assert(Assigned(Self));
  599. // to do: enforce that AValue is a valid IP address range
  600. if Going then
  601. begin
  602. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['IP Restriction'])); {do not localize??}
  603. end;
  604. FIPRestrictionVal := IPv4ToUInt32(AValue);
  605. FIPRestriction := AValue;
  606. end;
  607. procedure TIdHL7.SetPort(const AValue: Word);
  608. begin
  609. Assert(Assigned(Self));
  610. Assert(AValue <> 0, 'Attempt to use Port 0 for HL7 Communications'); {do not localize}
  611. if Going then
  612. begin
  613. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['Port'])); {do not localize}
  614. end;
  615. FPort := AValue;
  616. end;
  617. procedure TIdHL7.SetReconnectDelay(const AValue: LongWord);
  618. begin
  619. Assert(Assigned(Self));
  620. // any value for AValue is accepted, although this may not make sense
  621. if Going then
  622. begin
  623. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['Reconnect Delay'])); {do not localize}
  624. end;
  625. FReconnectDelay := AValue;
  626. end;
  627. procedure TIdHL7.SetTimeOut(const AValue: UInt32);
  628. begin
  629. Assert(Assigned(Self));
  630. Assert(AValue > 0, 'Attempt to configure TIdHL7 with a TimeOut of 0'); {do not localize}
  631. // we don't function at all if timeout is 0, though there are circumstances where it's not relevent
  632. if Going then
  633. begin
  634. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['Time Out'])); {do not localize??}
  635. end;
  636. FTimeOut := AValue;
  637. end;
  638. procedure TIdHL7.SetCommunicationMode(const AValue: THL7CommunicationMode);
  639. begin
  640. Assert(Assigned(Self));
  641. Assert((AValue >= Low(THL7CommunicationMode)) and (AValue <= High(THL7CommunicationMode)), 'Value for TIdHL7.CommunicationMode not in range'); {do not localize}
  642. // only could arise if someone is typecasting?
  643. if Going then
  644. begin
  645. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['Communication Mode'])); {do not localize}
  646. end;
  647. FCommunicationMode := AValue;
  648. end;
  649. procedure TIdHL7.SetIsListener(const AValue: Boolean);
  650. begin
  651. Assert(Assigned(Self));
  652. // AValue isn't checked
  653. if Going then
  654. begin
  655. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['IsListener'])); {do not localize}
  656. end;
  657. FIsListener := AValue;
  658. end;
  659. function TIdHL7.GetStatus: TIdHL7Status;
  660. begin
  661. Assert(Assigned(Self));
  662. Assert(Assigned(FLock));
  663. FLock.Enter;
  664. try
  665. Result := FStatus;
  666. finally
  667. FLock.Leave;
  668. end;
  669. end;
  670. function TIdHL7.Connected: Boolean;
  671. begin
  672. Assert(Assigned(Self));
  673. Assert(Assigned(FLock));
  674. FLock.Enter;
  675. try
  676. Result := (FStatus = IsConnected);
  677. finally
  678. FLock.Leave;
  679. end;
  680. end;
  681. function TIdHL7.GetStatusDesc: String;
  682. begin
  683. Assert(Assigned(Self));
  684. Assert(Assigned(FLock));
  685. FLock.Enter;
  686. try
  687. Result := FStatusDesc;
  688. finally
  689. FLock.Leave;
  690. end;
  691. end;
  692. procedure TIdHL7.InternalSetStatus(const AStatus: TIdHL7Status; ADesc: String);
  693. begin
  694. Assert(Assigned(Self));
  695. Assert((AStatus >= Low(TIdHL7Status)) and (AStatus <= High(TIdHL7Status)), 'Value for TIdHL7.CommunicationMode not in range'); {do not localize}
  696. // ADesc is allowed to be anything at all
  697. Assert(Assigned(FLock));
  698. FLock.Enter;
  699. try
  700. FStatus := AStatus;
  701. FStatusDesc := ADesc;
  702. finally
  703. FLock.Leave;
  704. end;
  705. end;
  706. {==========================================================
  707. Application Control
  708. ==========================================================}
  709. procedure TIdHL7.Start;
  710. var
  711. LStatus: TIdHL7Status;
  712. begin
  713. Assert(Assigned(Self));
  714. LStatus := GetStatus;
  715. if LStatus = IsUnusable then
  716. begin
  717. raise EHL7CommunicationError.Create(Name, RSHL7NotFailedToStop);
  718. end;
  719. if LStatus <> IsStopped then
  720. begin
  721. raise EHL7CommunicationError.Create(Name, RSHL7AlreadyStarted);
  722. end;
  723. if FCommunicationMode = cmUnknown then
  724. begin
  725. raise EHL7CommunicationError.Create(Name, RSHL7ModeNotSet);
  726. end;
  727. if FCommunicationMode = cmAsynchronous then
  728. begin
  729. if not Assigned(FOnMessageArrive) then
  730. begin
  731. raise EHL7CommunicationError.Create(Name, RSHL7NoAsynEvent);
  732. end;
  733. end;
  734. if (FCommunicationMode = cmSynchronous) and IsListener then
  735. begin
  736. if not Assigned(FOnReceiveMessage) then
  737. begin
  738. raise EHL7CommunicationError.Create(Name, RSHL7NoSynEvent);
  739. end;
  740. end;
  741. FIsServer := (FAddress = '');
  742. FPreStopped := False;
  743. FWaitingForAnswer := False;
  744. if FIsServer then
  745. begin
  746. StartServer;
  747. end else begin
  748. StartClient;
  749. end;
  750. end;
  751. procedure TIdHL7.PreStop;
  752. procedure JoltList(list: TInterfaceList);
  753. var
  754. i: Integer;
  755. begin
  756. for i := 0 to list.Count - 1 do
  757. begin
  758. IIdQueuedMessage(list[i]).SetEvent;
  759. end;
  760. end;
  761. begin
  762. Assert(Assigned(Self));
  763. if FCommunicationMode = cmSingleThread then
  764. begin
  765. Assert(Assigned(FLock));
  766. Assert(Assigned(FMsgQueue));
  767. Assert(Assigned(FHndMsgQueue));
  768. FLock.Enter;
  769. try
  770. JoltList(FMsgQueue);
  771. JoltList(FHndMsgQueue);
  772. finally
  773. FLock.Leave;
  774. end;
  775. end
  776. else if FCommunicationMode = cmSynchronous then
  777. begin
  778. Assert(Assigned(FWaitEvent));
  779. FWaitEvent.SetEvent;
  780. end;
  781. FPreStopped := True;
  782. end;
  783. procedure TIdHL7.Stop;
  784. begin
  785. Assert(Assigned(Self));
  786. if not Going then
  787. begin
  788. raise EHL7CommunicationError.Create(Name, RSHL7AlreadyStopped);
  789. end;
  790. if not FPreStopped then
  791. begin
  792. PreStop;
  793. IndySleep(10); // give other threads a chance to clean up
  794. end;
  795. if FIsServer then begin
  796. StopServer;
  797. end else begin
  798. StopClient;
  799. end;
  800. end;
  801. {==========================================================
  802. Server Connection Maintainance
  803. ==========================================================}
  804. procedure TIdHL7.EnforceWaitReplyTimeout;
  805. begin
  806. Stop;
  807. Start;
  808. end;
  809. function TIdHL7.Going: Boolean;
  810. var
  811. LStatus: TIdHL7Status;
  812. begin
  813. Assert(Assigned(Self));
  814. LStatus := GetStatus;
  815. Result := (LStatus <> IsStopped) and (LStatus <> IsUnusable);
  816. end;
  817. procedure TIdHL7.WaitForConnection(AMaxLength: UInt32);
  818. var
  819. LStartTime: TIdTicks;
  820. begin
  821. LStartTime := Ticks64;
  822. while (not Connected) and (GetElapsedTicks(LStartTime) < AMaxLength) do begin
  823. IndySleep(50);
  824. end;
  825. end;
  826. procedure TIdHL7.CheckSynchronousSendResult(AResult: TSendResponse; const AMsg: String);
  827. begin
  828. case AResult of
  829. srNone:
  830. raise EHL7CommunicationError.Create(Name, RSHL7ErrInternalsrNone);
  831. srError:
  832. raise EHL7CommunicationError.Create(Name, AMsg);
  833. srNoConnection:
  834. raise EHL7CommunicationError.Create(Name, RSHL7ErrNotConn);
  835. srSent:
  836. // cause this should only be returned asynchronously
  837. raise EHL7CommunicationError.Create(Name, RSHL7ErrInternalsrSent);
  838. srOK: ; // all ok
  839. srTimeout:
  840. raise EHL7CommunicationError.Create(Name, RSHL7ErrNoResponse);
  841. else
  842. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7ErrInternalUnknownVal, [Ord(AResult)]));
  843. end;
  844. end;
  845. procedure TIdHL7.SetConnectionTimeout(const AValue: UInt32);
  846. begin
  847. Assert(Assigned(Self));
  848. // any value for AValue is accepted, although this may not make sense
  849. if Going then
  850. begin
  851. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['Connection Timeout'])); {do not localize??}
  852. end;
  853. FConnectionTimeout := AValue;
  854. end;
  855. procedure TIdHL7.ReConnectFromTimeout;
  856. var
  857. iLoop : Integer;
  858. begin
  859. Assert(Assigned(Self));
  860. Assert(not FIsServer, 'Cannot try to reconnect from a timeout if acting as a server'); {do not localize}
  861. StartClient;
  862. IndySleep(50);
  863. iLoop := 0;
  864. while (not Connected) and (iLoop < 100) and (not FPreStopped) do
  865. begin
  866. IndySleep(100);
  867. Inc(iLoop);
  868. end;
  869. // TODO: raise an error if not connected or prestopped?
  870. end;
  871. procedure TIdHL7.SetKeepAlive(const AValue: TIdHL7KeepAlive);
  872. begin
  873. if Going then
  874. begin
  875. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['KeepAlive'])); {do not localize??}
  876. end;
  877. FKeepAlive.Assign(AValue);
  878. end;
  879. function TIdHL7.HasClientConnection: Boolean;
  880. begin
  881. Result := Assigned(FClientThread);
  882. end;
  883. procedure TIdHL7.Disconnect;
  884. var
  885. i: Integer;
  886. begin
  887. if FIsServer then
  888. begin
  889. FLock.Enter;
  890. try
  891. for i := 0 to FServerConnections.Count - 1 do begin
  892. TIdContext(FServerConnections[i]).Connection.Disconnect;
  893. end;
  894. finally
  895. FLock.Leave;
  896. end;
  897. end
  898. else if Assigned(FClientThread) then begin
  899. FClientThread.FClient.Disconnect;
  900. end;
  901. end;
  902. procedure TIdHL7.CheckServerParameters;
  903. begin
  904. Assert(Assigned(Self));
  905. if (FCommunicationMode = cmAsynchronous) or (not FIsListener) then
  906. begin
  907. FConnectionLimit := 1;
  908. end;
  909. if (FPort < 1) then // though we have already ensured that this cannot happen
  910. begin
  911. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7InvalidPort, [FPort]));
  912. end;
  913. end;
  914. procedure TIdHL7.StartServer;
  915. var
  916. i: Integer;
  917. begin
  918. Assert(Assigned(Self));
  919. CheckServerParameters;
  920. FServer := TIdTCPServer.Create(nil);
  921. try
  922. FServer.DefaultPort := FPort;
  923. FServer.OnConnect := ServerConnect;
  924. FServer.OnExecute := ServerExecute;
  925. FServer.OnDisconnect := ServerDisconnect;
  926. // RLebeau: this unit does not currently support restriction of IPv6 clients, so
  927. // adding an explicit IPv4 binding to prevent TIdTCPServer from creating an implicit
  928. // IPv6 binding on systems that allow dual IPv4/IPv6 bindings on the same ip/port...
  929. FServer.Bindings.Add.IPVersion := Id_IPv4; // TODO: support IPv6 clients?
  930. FServer.Active := True;
  931. if FKeepAlive.UseKeepAlive then
  932. begin
  933. for i := 0 to FServer.Bindings.Count - 1 do begin
  934. FServer.Bindings[i].SetKeepAliveValues(True, FKeepAlive.IdleTimeMS, FKeepAlive.IntervalMS);
  935. end;
  936. end;
  937. InternalSetStatus(IsNotConnected, RSHL7StatusNotConnected);
  938. except
  939. on e: Exception do
  940. begin
  941. InternalSetStatus(IsStopped, IndyFormat(RSHL7StatusFailedToStart, [e.Message]));
  942. FreeAndNil(FServer);
  943. raise;
  944. end;
  945. end;
  946. end;
  947. procedure TIdHL7.StopServer;
  948. begin
  949. Assert(Assigned(Self));
  950. try
  951. FServer.Active := False;
  952. FreeAndNil(FServer);
  953. InternalSetStatus(IsStopped, RSHL7StatusStopped);
  954. except
  955. on e: Exception do
  956. begin
  957. // somewhat arbitrary decision: if for some reason we fail to shutdown,
  958. // we will stubbornly refuse to work again.
  959. InternalSetStatus(IsUnusable, IndyFormat(RSHL7StatusFailedToStop, [e.Message]));
  960. FServer := nil; // Note: potential memory leak!
  961. raise;
  962. end;
  963. end;
  964. end;
  965. procedure TIdHL7.ServerConnect(AContext: TIdContext);
  966. var
  967. LNotify: Boolean;
  968. LConnCount: Integer;
  969. LValid: Boolean;
  970. LIPStr: String;
  971. LIPVal: UInt32;
  972. begin
  973. Assert(Assigned(Self));
  974. Assert(Assigned(AContext));
  975. Assert(Assigned(AContext.Binding));
  976. Assert(Assigned(FLock));
  977. LConnCount := 0;
  978. LIPStr := AContext.Binding.PeerIP;
  979. LIPVal := IPv4ToUInt32(LIPStr);
  980. if ((LIPVal xor FIPRestrictionVal) and FIPMaskVal) <> 0 then
  981. begin
  982. raise Exception.Create('Denied'); {do not localize}
  983. end;
  984. FLock.Enter;
  985. try
  986. LConnCount := FServerConnections.Count;
  987. LNotify := (LConnCount = 0);
  988. LValid := (LConnCount < FConnectionLimit);
  989. if LValid then
  990. begin
  991. if (LConnCount = 0) then
  992. begin
  993. FServerConn := AContext.Connection;
  994. end else begin
  995. FServerConn := nil; // RLebeau: why?
  996. end;
  997. FServerConnections.Add(AContext);
  998. Inc(LConnCount);
  999. if LNotify then
  1000. begin
  1001. InternalSetStatus(IsConnected, RSHL7StatusConnected);
  1002. end;
  1003. AContext.Connection.IOHandler.ReadTimeout := FReceiveTimeout;
  1004. end;
  1005. finally
  1006. FLock.Leave;
  1007. end;
  1008. if LValid then
  1009. begin
  1010. if LNotify and Assigned(FOnConnect) then begin
  1011. FOnConnect(self);
  1012. end;
  1013. if Assigned(FOnConnCountChange) and (FConnectionLimit <> 1) then begin
  1014. FOnConnCountChange(Self, LConnCount);
  1015. end;
  1016. end else begin
  1017. // Thread exceeds connection limit
  1018. // it would be better to stop getting here in the case of an invalid connection
  1019. // cause here we drop it - nasty for the client. To be investigated later
  1020. AContext.Connection.Disconnect;
  1021. end;
  1022. end;
  1023. procedure TIdHL7.ServerDisconnect(AContext: TIdContext);
  1024. var
  1025. LNotify: Boolean;
  1026. LConnCount: Integer;
  1027. begin
  1028. Assert(Assigned(Self));
  1029. Assert(Assigned(AContext));
  1030. Assert(Assigned(FLock));
  1031. FLock.Enter;
  1032. try
  1033. FServerConnections.Remove(AContext);
  1034. LConnCount := FServerConnections.Count;
  1035. LNotify := (LConnCount = 0);
  1036. if AContext.Connection = FServerConn then
  1037. begin
  1038. FServerConn := nil;
  1039. end;
  1040. if LNotify then
  1041. begin
  1042. InternalSetStatus(IsNotConnected, RSHL7StatusNotConnected);
  1043. end;
  1044. finally
  1045. FLock.Leave;
  1046. end;
  1047. //Note events outside of critical section as they are expected to have critical thread save logic build into them
  1048. if Assigned(FOnConnCountChange) and (FConnectionLimit <> 1) then begin
  1049. FOnConnCountChange(Self, LConnCount); //Current causes Thread to freeze if called event does something like write to memobox even if in a critical section
  1050. end;
  1051. if LNotify and Assigned(FOnDisconnect) then begin
  1052. FOnDisconnect(Self); //Current causes Thread to freeze if called event does something like write to memobox even if in a critical section
  1053. end;
  1054. end;
  1055. procedure TIdHL7.ServerExecute(AContext: TIdContext);
  1056. var
  1057. LBuffer: TIdBytes;
  1058. begin
  1059. Assert(Assigned(Self));
  1060. Assert(Assigned(AContext));
  1061. FIsServerExecuting := True;
  1062. try
  1063. // 1. prompt the network for content.
  1064. while Assigned(AContext.Connection.IOHandler) do
  1065. begin
  1066. AContext.Connection.IOHandler.ReadBytes(LBuffer, -1, True);
  1067. HandleIncoming(LBuffer, AContext.Connection);
  1068. end;
  1069. except
  1070. try
  1071. // well, there was some network error. We aren't sure what it
  1072. // was, and it doesn't matter for this layer. we're just going
  1073. // to make sure that we start again.
  1074. // to review: what happens to the error messages?
  1075. AContext.Connection.Disconnect;
  1076. except
  1077. end;
  1078. end;
  1079. FIsServerExecuting := False;
  1080. end;
  1081. procedure TIdHL7.DropServerConnection;
  1082. begin
  1083. Assert(Assigned(Self));
  1084. Assert(Assigned(FLock));
  1085. FLock.Enter;
  1086. try
  1087. if Assigned(FServerConn) then begin
  1088. FServerConn.Disconnect;
  1089. end;
  1090. finally
  1091. FLock.Leave;
  1092. end;
  1093. end;
  1094. {==========================================================
  1095. Client Connection Maintainance
  1096. ==========================================================}
  1097. procedure TIdHL7.CheckClientParameters;
  1098. begin
  1099. Assert(Assigned(Self));
  1100. if (FPort < 1) then
  1101. begin
  1102. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7InvalidPort, [FPort]));
  1103. end;
  1104. end;
  1105. procedure TIdHL7.StartClient;
  1106. begin
  1107. Assert(Assigned(Self));
  1108. CheckClientParameters;
  1109. FClientThread := TIdHL7ClientThread.Create(Self);
  1110. InternalSetStatus(isConnecting, RSHL7StatusConnecting);
  1111. end;
  1112. procedure TIdHL7.StopClient;
  1113. var
  1114. LFinished: Boolean;
  1115. LStartTime: TIdTicks;
  1116. begin
  1117. Assert(Assigned(Self));
  1118. Assert(Assigned(FLock));
  1119. FLock.Enter;
  1120. try
  1121. if Assigned(FClientThread) then
  1122. begin
  1123. FClientThread.Terminate;
  1124. FClientThread.FClient.Disconnect;
  1125. FClientThread.FCloseEvent.SetEvent;
  1126. end else begin
  1127. InternalSetStatus(isStopped, 'Stopped'); {do not localize}
  1128. end;
  1129. finally
  1130. FLock.Leave;
  1131. end;
  1132. LStartTime := Ticks64;
  1133. repeat
  1134. LFinished := (GetStatus = IsStopped);
  1135. if not LFinished then begin
  1136. IndySleep(10);
  1137. end;
  1138. until LFinished or (GetElapsedTicks(LStartTime) > WAIT_STOP);
  1139. if GetStatus <> IsStopped then
  1140. begin
  1141. // for some reason the client failed to shutdown. We will stubbornly refuse to work again
  1142. InternalSetStatus(IsUnusable, IndyFormat(RSHL7StatusFailedToStop, [RSHL7ClientThreadNotStopped]));
  1143. end;
  1144. end;
  1145. procedure TIdHL7.DropClientConnection;
  1146. begin
  1147. Assert(Assigned(Self));
  1148. Assert(Assigned(FLock));
  1149. FLock.Enter;
  1150. try
  1151. if Assigned(FClientThread) then begin
  1152. FClientThread.FClient.Disconnect;
  1153. end else begin
  1154. // This may happen validly because both ends are trying to drop the connection simultaineously
  1155. end;
  1156. finally
  1157. FLock.Leave;
  1158. end;
  1159. end;
  1160. { TIdHL7ClientThread }
  1161. constructor TIdHL7ClientThread.Create(AOwner: TIdHL7);
  1162. begin
  1163. Assert(Assigned(AOwner));
  1164. FOwner := AOwner;
  1165. FCloseEvent := TIdLocalEvent.Create(True, False);
  1166. FClient := TIdTCPClient.Create(nil);
  1167. FClient.Host := AOwner.Address;
  1168. FClient.Port := AOwner.Port;
  1169. FClient.ReadTimeout := AOwner.ReceiveTimeout;
  1170. FClient.UseNagle := True;
  1171. inherited Create(False);
  1172. FreeOnTerminate := True;
  1173. end;
  1174. destructor TIdHL7ClientThread.Destroy;
  1175. begin
  1176. Assert(Assigned(Self));
  1177. Assert(Assigned(FOwner));
  1178. Assert(Assigned(FOwner.FLock));
  1179. try
  1180. FOwner.FLock.Enter;
  1181. try
  1182. FOwner.FClientThread := nil;
  1183. if not TimedOut then begin
  1184. FOwner.InternalSetStatus(isStopped, RSHL7StatusStopped);
  1185. end;
  1186. finally
  1187. FOwner.FLock.Leave;
  1188. end;
  1189. except
  1190. // it's really vaguely possible that the owner
  1191. // may be dead before we are. If that is the case, we blow up here.
  1192. // who cares.
  1193. end;
  1194. FCloseEvent.Free;
  1195. FClient.Free;
  1196. inherited;
  1197. end;
  1198. procedure TIdHL7ClientThread.PollStack;
  1199. var
  1200. LBuffer: TIdBytes;
  1201. begin
  1202. Assert(Assigned(Self));
  1203. repeat
  1204. // we don't send here - we just poll the stack for content
  1205. // if the application wants to terminate us at this point,
  1206. // then it will disconnect the socket and we will get thrown
  1207. // out
  1208. // we really don't care at all whether the disconnect was clean or ugly
  1209. // but we do need to suppress exceptions that come from
  1210. // indy otherwise the client thread will terminate
  1211. try
  1212. while Assigned(FClient.IOHandler) do
  1213. begin
  1214. FClient.IOHandler.ReadBytes(LBuffer, -1, True);
  1215. FOwner.HandleIncoming(LBuffer, FClient);
  1216. end;
  1217. except
  1218. try
  1219. // well, there was some network error. We aren't sure what it
  1220. // was, and it doesn't matter for this layer. we're just going
  1221. // to make sure that we start again.
  1222. // to review: what happens to the error messages?
  1223. FClient.Disconnect;
  1224. except
  1225. end;
  1226. end;
  1227. until Terminated or (not FClient.Connected);
  1228. end;
  1229. const
  1230. SECOND_LENGTH = 1000;
  1231. MINUTE_LENGTH = SECOND_LENGTH * 60;
  1232. HOUR_LENGTH = MINUTE_LENGTH * 60;
  1233. DAY_LENGTH = HOUR_LENGTH * 24;
  1234. function DescribePeriod(Period: LongWord): String;
  1235. begin
  1236. if Period < SECOND_LENGTH then begin
  1237. Result := IntToStr(Period) + 'ms' {do not localize}
  1238. end
  1239. else if Period < (180 * SECOND_LENGTH) then begin
  1240. Result := IntToStr(trunc(Period / SECOND_LENGTH)) + 'sec' {do not localize}
  1241. end
  1242. else if Period < (180 * MINUTE_LENGTH) then begin
  1243. Result := IntToStr(trunc(Period / MINUTE_LENGTH)) + 'min' {do not localize}
  1244. end
  1245. else if Period < (72 * HOUR_LENGTH) then begin
  1246. Result := IntToStr(trunc(Period / HOUR_LENGTH)) + 'hr' {do not localize}
  1247. end else begin
  1248. Result := IntToStr(trunc(Period / DAY_LENGTH)) + ' days'; {do not localize}
  1249. end;
  1250. end;
  1251. procedure TIdHL7ClientThread.Execute;
  1252. begin
  1253. Assert(Assigned(Self));
  1254. try
  1255. repeat
  1256. // try to connect. Try indefinitely but wait Owner.FReconnectDelay
  1257. // between attempts. Problems: how long does Connect take?
  1258. repeat
  1259. FOwner.InternalSetStatus(IsConnecting, rsHL7StatusConnecting);
  1260. try
  1261. FClient.Connect;
  1262. Break;
  1263. except
  1264. on e: Exception do
  1265. begin
  1266. //now we can take more liberties with the time and date output because it's only
  1267. //for human consumption (probably in a log
  1268. FOwner.InternalSetStatus(IsWaitReconnect, IndyFormat(rsHL7StatusReConnect, [DescribePeriod(FOwner.FReconnectDelay), e.Message]));
  1269. end;
  1270. end;
  1271. if Terminated then Break;
  1272. // TODO: run this in a smaller loop checking Terminated on each iteration,
  1273. // or hook up this event to TThread.TerminatedSet()...
  1274. FCloseEvent.WaitFor(FOwner.FReconnectDelay);
  1275. until Terminated;
  1276. if Terminated then begin
  1277. Exit;
  1278. end;
  1279. if FOwner.FKeepAlive.UseKeepAlive then begin
  1280. FClient.Socket.Binding.SetKeepAliveValues(True, FOwner.FKeepAlive.IdleTimeMS, FOwner.FKeepAlive.IntervalMS);
  1281. end;
  1282. FLastTraffic := Ticks64;
  1283. FOwner.FLock.Enter;
  1284. try
  1285. FOwner.FClient := FClient;
  1286. FOwner.InternalSetStatus(IsConnected, rsHL7StatusConnected);
  1287. finally
  1288. FOwner.FLock.Leave;
  1289. end;
  1290. if Assigned(FOwner.FOnConnect) then begin
  1291. FOwner.FOnConnect(FOwner);
  1292. end;
  1293. try
  1294. PollStack;
  1295. finally
  1296. FOwner.FLock.Enter;
  1297. try
  1298. FOwner.FClient := nil;
  1299. if TimedOut then begin
  1300. FOwner.InternalSetStatus(isTimedOut, RSHL7StatusTimedout);
  1301. end else begin
  1302. FOwner.InternalSetStatus(IsNotConnected, RSHL7StatusNotConnected);
  1303. end;
  1304. finally
  1305. FOwner.FLock.Leave;
  1306. end;
  1307. if Assigned(FOwner.FOnDisconnect) then begin
  1308. FOwner.FOnDisconnect(FOwner);
  1309. end;
  1310. end;
  1311. if TimedOut then begin
  1312. FClient.Disconnect;
  1313. end
  1314. else if not Terminated then
  1315. begin
  1316. // we got disconnected. ReconnectDelay applies.
  1317. FOwner.InternalSetStatus(IsWaitReconnect, IndyFormat(rsHL7StatusReConnect, [DescribePeriod(FOwner.FReconnectDelay), 'Disconnected'])); {do not localize}
  1318. // TODO: run this in a smaller loop checking Terminated on each iteration,
  1319. // or hook up this event to TThread.TerminatedSet()...
  1320. FCloseEvent.WaitFor(FOwner.FReconnectDelay);
  1321. end;
  1322. until Terminated or (not FOwner.IsListener and TimedOut);
  1323. except
  1324. on e: Exception do
  1325. begin
  1326. // presumably some comms or indy related exception
  1327. // there's not really any good place to put this????
  1328. end;
  1329. end;
  1330. end;
  1331. function TIdHL7ClientThread.TimedOut: Boolean;
  1332. begin
  1333. Result := (FOwner.FConnectionTimeout > 0) and (GetElapsedTicks(FLastTraffic) > FOwner.FConnectionTimeout);
  1334. end;
  1335. {==========================================================
  1336. Internal process management
  1337. ==========================================================}
  1338. function EncodeHL7Message(const AMsg: String; AByteEncoding: IIdTextEncoding): TIdBytes;
  1339. var
  1340. LMsgLen, LIndex: Integer;
  1341. begin
  1342. LMsgLen := AByteEncoding.GetByteCount(AMsg);
  1343. SetLength(Result, Length(MSG_START) + LMsgLen + Length(MSG_END));
  1344. LIndex := 0;
  1345. CopyTIdByteArray(MSG_START, 0, Result, LIndex, Length(MSG_START));
  1346. Inc(LIndex, Length(MSG_START));
  1347. AByteEncoding.GetBytes(AMsg, 1, Length(AMsg), Result, LIndex);
  1348. Inc(LIndex, LMsgLen);
  1349. CopyTIdByteArray(MSG_END, 0, Result, LIndex, Length(MSG_END));
  1350. end;
  1351. procedure TIdHL7.HandleIncoming(var VBuffer: TIdBytes;
  1352. AConnection: TIdTCPConnection);
  1353. var
  1354. LStart, LEnd: Integer;
  1355. LMsg, LReply: String;
  1356. LBytes: TIdBytes;
  1357. function FindBytes(const ABytesToSearch: TIdBytes; const ABytesToFind: array of Byte; AStart: Integer): Integer;
  1358. var
  1359. I: Integer;
  1360. LBytesLen, LFindLen: Integer;
  1361. LMatches: Boolean;
  1362. begin
  1363. LBytesLen := Length(ABytesToSearch);
  1364. LFindLen := Length(ABytesToFind);
  1365. while (AStart + LFindLen) <= LBytesLen do
  1366. begin
  1367. Result := ByteIndex(ABytesToFind[0], ABytesToSearch, AStart);
  1368. if Result = -1 then Exit;
  1369. LMatches := True;
  1370. for I := 1 to High(ABytesToFind) do
  1371. begin
  1372. if ABytesToSearch[Result + I] <> ABytesToFind[I] then
  1373. begin
  1374. LMatches := False;
  1375. Break;
  1376. end;
  1377. end;
  1378. if LMatches then Exit;
  1379. Inc(AStart);
  1380. end;
  1381. Result := -1;
  1382. end;
  1383. begin
  1384. Assert(Assigned(Self));
  1385. Assert(Length(VBuffer) > 0, 'Attempt to handle an empty buffer'); {do not localize}
  1386. Assert(Assigned(AConnection));
  1387. try
  1388. // process any messages in the buffer (may get more than one per packet)
  1389. repeat
  1390. LStart := FindBytes(VBuffer, MSG_START, 0);
  1391. if LStart >= 0 then begin
  1392. Inc(LStart, Length(MSG_START));
  1393. LEnd := FindBytes(VBuffer, MSG_END, LStart);
  1394. end else begin
  1395. LEnd := FindBytes(VBuffer, MSG_END, 0);
  1396. end;
  1397. if (LStart >= 0) and (LEnd >= 0) then
  1398. begin
  1399. LMsg := FDefStringEncoding.GetString(VBuffer, LStart, LEnd - LStart);
  1400. if HandleMessage(LMsg, AConnection, LReply) then
  1401. begin
  1402. if Length(LReply) > 0 then
  1403. begin
  1404. LBytes := EncodeHL7Message(LReply, FDefStringEncoding);
  1405. AConnection.IOHandler.Write(LBytes);
  1406. end;
  1407. end else begin
  1408. AConnection.Disconnect;
  1409. end;
  1410. end;
  1411. if LEnd >= 0 then begin
  1412. VBuffer := Copy(VBuffer, LEnd + Length(MSG_END), MaxInt);
  1413. end;
  1414. until LEnd = -1;
  1415. if Length(VBuffer) > BUFFER_SIZE_LIMIT then begin
  1416. AConnection.Disconnect;
  1417. end;
  1418. except
  1419. // well, we need to suppress the exception, and force a reconnection
  1420. // we don't know why an exception has been allowed to propagate back
  1421. // to us, it shouldn't be allowed. so what we're going to do, is drop
  1422. // the connection so that we force all the network layers on both
  1423. // ends to reconnect.
  1424. // this is a waste of time if the error came from the application but
  1425. // this is not supposed to happen
  1426. try
  1427. AConnection.Disconnect;
  1428. except
  1429. // nothing - suppress
  1430. end;
  1431. end;
  1432. end;
  1433. function TIdHL7.HandleMessage(const AMsg: String; AConn: TIdTCPConnection; var VReply: String): Boolean;
  1434. var
  1435. LQueMsg: IIdQueuedMessage;
  1436. begin
  1437. Assert(Assigned(Self));
  1438. Assert(Length(AMsg) > 0, 'Attempt to handle an empty message'); {do not localize}
  1439. Assert(Assigned(FLock));
  1440. VReply := '';
  1441. Result := True;
  1442. try
  1443. case FCommunicationMode of
  1444. cmUnknown:
  1445. begin
  1446. raise EHL7CommunicationError.Create(Name, RSHL7ImpossibleMessage);
  1447. end;
  1448. cmAsynchronous:
  1449. begin
  1450. FOnMessageArrive(Self, AConn, AMsg);
  1451. end;
  1452. cmSynchronous, cmSingleThread:
  1453. begin
  1454. if IsListener then
  1455. begin
  1456. if FCommunicationMode = cmSynchronous then
  1457. begin
  1458. Result := False;
  1459. FOnReceiveMessage(Self, AConn, AMsg, Result, VReply);
  1460. end else
  1461. begin
  1462. LQueMsg := TIdQueuedMessage.Create(AMsg, FReceiveTimeout);
  1463. try
  1464. FLock.Enter;
  1465. try
  1466. FMsgQueue.Add(LQueMsg);
  1467. finally
  1468. FLock.Leave;
  1469. end;
  1470. LQueMsg.Wait;
  1471. // no locking. There is potential problems here. To be reviewed
  1472. VReply := LQueMsg.Reply;
  1473. finally
  1474. FLock.Enter;
  1475. try
  1476. FMsgQueue.Remove(LQueMsg);
  1477. finally
  1478. FLock.Leave;
  1479. end;
  1480. LQueMsg := nil;
  1481. end;
  1482. end;
  1483. end else
  1484. begin
  1485. FLock.Enter;
  1486. try
  1487. if FWaitingForAnswer then
  1488. begin
  1489. FWaitingForAnswer := False;
  1490. FMsgReply := AMsg;
  1491. FReplyResponse := srOK;
  1492. if FCommunicationMode = cmSynchronous then
  1493. begin
  1494. Assert(Assigned(FWaitEvent));
  1495. FWaitEvent.SetEvent;
  1496. end;
  1497. end else begin
  1498. // we could have got here by timing out, but this is quite unlikely,
  1499. // since the connection will be dropped in that case. We will report
  1500. // this as a spurious message
  1501. raise EHL7CommunicationError.Create(Name, RSHL7UnexpectedMessage);
  1502. end;
  1503. finally
  1504. FLock.Leave;
  1505. end;
  1506. end;
  1507. end;
  1508. else
  1509. begin
  1510. raise EHL7CommunicationError.Create(Name, RSHL7UnknownMode);
  1511. end;
  1512. end;
  1513. except
  1514. on e: Exception do
  1515. begin
  1516. if Assigned(FOnReceiveError) then begin
  1517. FOnReceiveError(Self, AConn, AMsg, e, VReply, Result);
  1518. end else begin
  1519. Result := False;
  1520. end;
  1521. end;
  1522. end;
  1523. end;
  1524. {==========================================================
  1525. Sending
  1526. ==========================================================}
  1527. // this procedure is not technically thread safe.
  1528. // if the connection is disappearing when we are attempting
  1529. // to write, we can get transient access violations. Several
  1530. // strategies are available to prevent this but they significantly
  1531. // increase the scope of the locks, which costs more than it gains
  1532. function TIdHL7.AsynchronousSend(const AMsg: String; ASyncConnection: TIdTCPConnection = nil): TSendResponse;
  1533. var
  1534. LBytes: TIdBytes;
  1535. begin
  1536. Result := srNone; // just to suppress the compiler warning
  1537. Assert(Assigned(Self));
  1538. Assert(Length(AMsg) > 0, 'Attempt to send an empty message'); {do not localize}
  1539. Assert(Assigned(FLock));
  1540. if GetStatus = isTimedOut then
  1541. begin
  1542. ReConnectFromTimeout;
  1543. end;
  1544. FLock.Enter;
  1545. try
  1546. if not Going then
  1547. begin
  1548. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWorking, [RSHL7SendMessage]));
  1549. end
  1550. else if GetStatus <> isConnected then
  1551. begin
  1552. Result := srNoConnection;
  1553. end
  1554. else if FIsServer then
  1555. begin
  1556. if (FCommunicationMode = cmAsynchronous) and Assigned(ASyncConnection) then
  1557. begin
  1558. LBytes := EncodeHL7Message(AMsg, FDefStringEncoding);
  1559. ASyncConnection.IOHandler.Write(LBytes);
  1560. Result := srSent;
  1561. end
  1562. else if Assigned(FServerConn) then
  1563. begin
  1564. LBytes := EncodeHL7Message(AMsg, FDefStringEncoding);
  1565. FServerConn.IOHandler.Write(LBytes);
  1566. Result := srSent;
  1567. end else begin
  1568. raise EHL7CommunicationError.Create(Name, RSHL7NoConnectionFound);
  1569. end;
  1570. end
  1571. else if Assigned(FClientThread) and Assigned(FClient) then
  1572. begin
  1573. LBytes := EncodeHL7Message(AMsg, FDefStringEncoding);
  1574. FClient.IOHandler.Write(LBytes);
  1575. FClientThread.FLastTraffic := Ticks64; // TODO: sync this?
  1576. Result := srSent;
  1577. end else begin
  1578. raise EHL7CommunicationError.Create(Name, RSHL7NoConnectionFound);
  1579. end;
  1580. finally
  1581. FLock.Leave;
  1582. end;
  1583. end;
  1584. function TIdHL7.SynchronousSend(const AMsg: String; var VReply: String): TSendResponse;
  1585. begin
  1586. Assert(Assigned(self));
  1587. Assert(Length(AMsg) > 0, 'Attempt to send an empty message'); {do not localize}
  1588. Assert(Assigned(FLock));
  1589. Result := srError;
  1590. FLock.Enter;
  1591. try
  1592. FWaitingForAnswer := True;
  1593. FWaitStart := Ticks64;
  1594. FReplyResponse := srTimeout;
  1595. FMsgReply := '';
  1596. finally
  1597. FLock.Leave;
  1598. end;
  1599. try
  1600. Result := AsynchronousSend(AMsg);
  1601. if Result = srSent then
  1602. begin
  1603. Assert(Assigned(FWaitEvent));
  1604. FWaitEvent.WaitFor(FTimeOut);
  1605. end;
  1606. finally
  1607. FLock.Enter;
  1608. try
  1609. FWaitingForAnswer := False;
  1610. if Result = srSent then
  1611. begin
  1612. Result := FReplyResponse;
  1613. end;
  1614. if Result = srTimeout then
  1615. begin
  1616. if FIsServer then begin
  1617. DropServerConnection;
  1618. end else begin
  1619. DropClientConnection;
  1620. end;
  1621. end;
  1622. VReply := FMsgReply;
  1623. finally
  1624. FLock.Leave;
  1625. end;
  1626. end;
  1627. end;
  1628. procedure TIdHL7.SendMessage(const AMsg: String);
  1629. begin
  1630. Assert(Assigned(Self));
  1631. Assert(Length(AMsg) > 0, 'Attempt to send an empty message'); {do not localize}
  1632. Assert(Assigned(FLock));
  1633. if FWaitingForAnswer then begin
  1634. raise EHL7CommunicationError.Create(Name, RSHL7WaitForAnswer);
  1635. end;
  1636. FLock.Enter;
  1637. try
  1638. FWaitingForAnswer := True;
  1639. FWaitStart := Ticks64;
  1640. FMsgReply := '';
  1641. FReplyResponse := AsynchronousSend(AMsg);
  1642. finally
  1643. FLock.Leave;
  1644. end;
  1645. end;
  1646. function TIdHL7.GetReply(var VReply: String): TSendResponse;
  1647. begin
  1648. Assert(Assigned(Self));
  1649. Assert(Assigned(FLock));
  1650. FLock.Enter;
  1651. try
  1652. if FWaitingForAnswer then
  1653. begin
  1654. if GetElapsedTicks(FWaitStart) > FTimeOut then
  1655. begin
  1656. Result := srTimeout;
  1657. VReply := '';
  1658. FWaitingForAnswer := False;
  1659. FReplyResponse := srError;
  1660. end else begin
  1661. Result := srNone;
  1662. end;
  1663. end else
  1664. begin
  1665. Result := FReplyResponse;
  1666. if Result = srSent then
  1667. begin
  1668. Result := srTimeOut;
  1669. end;
  1670. VReply := FMsgReply;
  1671. FWaitingForAnswer := False;
  1672. FReplyResponse := srError;
  1673. end;
  1674. finally
  1675. FLock.Leave;
  1676. end;
  1677. end;
  1678. function TIdHL7.GetMessage(var VMsg: String): IInterface;
  1679. var
  1680. LQueMsg: IIdQueuedMessage;
  1681. begin
  1682. Result := nil;
  1683. Assert(Assigned(Self));
  1684. Assert(Assigned(FLock));
  1685. Assert(Assigned(FMsgQueue));
  1686. FLock.Enter;
  1687. try
  1688. if FMsgQueue.Count > 0 then
  1689. begin
  1690. LQueMsg := IIdQueuedMessage(FMsgQueue[0]);
  1691. VMsg := LQueMsg.Message;
  1692. FMsgQueue.Delete(0);
  1693. FHndMsgQueue.Add(LQueMsg);
  1694. Result := LQueMsg;
  1695. end;
  1696. finally
  1697. FLock.Leave;
  1698. end;
  1699. end;
  1700. procedure TIdHL7.SendReply(AMsgHnd: IInterface; const AReply: String);
  1701. var
  1702. LQueMsg: IIdQueuedMessage;
  1703. begin
  1704. Assert(Assigned(Self));
  1705. Assert(Assigned(AMsgHnd));
  1706. Assert(Length(AReply) > 0, 'Attempt to send an empty reply'); {do not localize}
  1707. Assert(Assigned(FLock));
  1708. FLock.Enter;
  1709. try
  1710. LQueMsg := AMsgHnd as IIdQueuedMessage;
  1711. LQueMsg.Reply := AReply;
  1712. LQueMsg.SetEvent;
  1713. FHndMsgQueue.Remove(LQueMsg);
  1714. finally
  1715. FLock.Leave;
  1716. end;
  1717. end;
  1718. end.