IdHL7.pas 61 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006
  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. {$IFDEF STRING_IS_ANSI}
  244. FDefAnsiEncoding: IIdTextEncoding;
  245. {$ENDIF}
  246. procedure SetAddress(const AValue: String);
  247. procedure SetKeepAlive(const AValue: TIdHL7KeepAlive);
  248. procedure SetConnectionLimit(const AValue: Word);
  249. procedure SetIPMask(const AValue: String);
  250. procedure SetIPRestriction(const AValue: String);
  251. procedure SetPort(const AValue: Word);
  252. procedure SetReconnectDelay(const AValue: LongWord);
  253. procedure SetConnectionTimeout(const AValue: UInt32);
  254. procedure SetTimeOut(const AValue: UInt32);
  255. procedure SetCommunicationMode(const AValue: THL7CommunicationMode);
  256. procedure SetIsListener(const AValue: Boolean);
  257. procedure SetDefStringEncoding(const AValue: IIdTextEncoding);
  258. {$IFDEF STRING_IS_ANSI}
  259. procedure SetDefAnsiEncoding(const AValue: IIdTextEncoding);
  260. {$ENDIF}
  261. function GetStatus: TIdHL7Status;
  262. function GetStatusDesc: String;
  263. procedure InternalSetStatus(const AStatus: TIdHL7Status; ADesc: String);
  264. procedure CheckServerParameters;
  265. procedure StartServer;
  266. procedure StopServer;
  267. procedure DropServerConnection;
  268. procedure ServerConnect(AContext: TIdContext);
  269. procedure ServerExecute(AContext: TIdContext);
  270. procedure ServerDisconnect(AContext: TIdContext);
  271. procedure CheckClientParameters;
  272. procedure StartClient;
  273. procedure StopClient;
  274. procedure DropClientConnection;
  275. procedure ReConnectFromTimeout;
  276. procedure HandleIncoming(var VBuffer: TIdBytes; AConnection: TIdTCPConnection);
  277. function HandleMessage(const AMsg: String; AConn: TIdTCPConnection; var VReply: String): Boolean;
  278. procedure InitComponent; override;
  279. public
  280. {$IFDEF WORKAROUND_INLINE_CONSTRUCTORS}
  281. constructor Create(AOwner: TComponent); reintroduce; overload;
  282. {$ENDIF}
  283. destructor Destroy; override;
  284. procedure EnforceWaitReplyTimeout;
  285. function Going: Boolean;
  286. // for the app to use to hold any related object
  287. property ObjTag: TObject read FObject write FObject;
  288. // status
  289. property Status: TIdHL7Status read GetStatus;
  290. property StatusDesc: String read GetStatusDesc;
  291. function Connected: Boolean;
  292. property IsServer: Boolean read FIsServer;
  293. procedure Start;
  294. procedure PreStop; // call this in advance to start the shut down process. You do not need to call this
  295. procedure Stop;
  296. procedure WaitForConnection(AMaxLength: UInt32); // milliseconds
  297. // asynchronous.
  298. function AsynchronousSend(const AMsg: String; ASyncConnection: TIdTCPConnection = nil): TSendResponse;
  299. property OnMessageArrive: TMessageArriveEvent read FOnMessageArrive write FOnMessageArrive;
  300. // synchronous
  301. function SynchronousSend(const AMsg: String; var VReply: String): TSendResponse;
  302. property OnReceiveMessage: TMessageReceiveEvent read FOnReceiveMessage write FOnReceiveMessage;
  303. procedure CheckSynchronousSendResult(AResult: TSendResponse; const AMsg: String);
  304. // single thread - like SynchronousSend, but don't hold the thread waiting
  305. procedure SendMessage(const AMsg: String);
  306. // you can't call SendMessage again without calling GetReply first
  307. function GetReply(var VReply: String): TSendResponse;
  308. function GetMessage(var VMsg: String): IInterface; // return nil if no messages
  309. // if you don't call SendReply then no reply will be sent.
  310. procedure SendReply(AMsgHnd: IInterface; const AReply: String);
  311. function HasClientConnection : Boolean;
  312. procedure Disconnect;
  313. property DefStringEncoding: IIdTextEncoding read FDefStringEncoding write SetDefStringEncoding;
  314. {$IFDEF STRING_IS_ANSI}
  315. property DefAnsiEncoding: IIdTextEncoding read FDefAnsiEncoding write SetDefAnsiEncoding;
  316. {$ENDIF}
  317. property IsServerExecuting: Boolean read FIsServerExecuting;
  318. published
  319. // basic properties
  320. property Address: String read FAddress write SetAddress; // leave blank and we will be server
  321. property Port: Word read FPort write SetPort default DEFAULT_PORT;
  322. property KeepAlive: TIdHL7KeepAlive read FKeepAlive write SetKeepAlive;
  323. // milliseconds - message timeout - how long we wait for other system to reply
  324. property TimeOut: UInt32 read FTimeOut write SetTimeOut default DEFAULT_TIMEOUT;
  325. // milliseconds - message timeout. When running cmSingleThread, how long we wait for the application to process an incoming message before giving up
  326. property ReceiveTimeout: LongWord read FReceiveTimeout write FReceiveTimeout default DEFAULT_RECEIVE_TIMEOUT;
  327. // server properties
  328. property ConnectionLimit: Word read FConnectionLimit write SetConnectionLimit default DEFAULT_CONN_LIMIT; // ignored if isListener is false
  329. property IPRestriction: String read FIPRestriction write SetIPRestriction;
  330. property IPMask: String read FIPMask write SetIPMask;
  331. // client properties
  332. // milliseconds - how long we wait after losing connection to retry
  333. property ReconnectDelay: LongWord read FReconnectDelay write SetReconnectDelay default DEFAULT_RECONNECT_DELAY;
  334. // milliseconds - how long we allow a connection to be open without traffic (damn firewalls)
  335. property ConnectionTimeout: UInt32 read FConnectionTimeout write SetConnectionTimeout default DEFAULT_CONNECTION_TIMEOUT;
  336. // message flow
  337. // Set this to one of 4 possibilities:
  338. //
  339. // cmUnknown
  340. // Default at start up. You must set a value before starting
  341. //
  342. // cmAsynchronous
  343. // Send Messages with AsynchronousSend. does not wait for
  344. // remote side to respond before returning
  345. // Receive Messages with OnMessageArrive. Message may
  346. // be response or new message
  347. // The application is responsible for responding to the remote
  348. // application and dropping the link as required
  349. // You must hook the OnMessageArrive Event before setting this mode
  350. // The property IsListener has no meaning in this mode
  351. //
  352. // cmSynchronous
  353. // Send Messages with SynchronousSend. Remote applications response
  354. // will be returned (or timeout). Only use if IsListener is false
  355. // Receive Messages with OnReceiveMessage. Only if IsListener is
  356. // true
  357. // In this mode, the object will wait for a response when sending,
  358. // and expects the application to reply when a message arrives.
  359. // In this mode, the interface can either be the listener or the
  360. // initiator but not both. IsListener controls which one.
  361. // note that OnReceiveMessage must be thread safe if you allow
  362. // more than one connection to a server
  363. //
  364. // cmSingleThread
  365. // Send Messages with SendMessage. Poll for answer using GetReply.
  366. // Only if isListener is false
  367. // Receive Messages using GetMessage. Return a response using
  368. // SendReply. Only if IsListener is true
  369. // This mode is the same as cmSynchronous, but the application is
  370. // assumed to be single threaded. The application must poll to
  371. // find out what is happening rather than being informed using
  372. // an event in a different thread
  373. property CommunicationMode: THL7CommunicationMode read FCommunicationMode write SetCommunicationMode default DEFAULT_COMM_MODE;
  374. // note that IsListener is not related to which end is client. Either end
  375. // may make the connection, and thereafter only one end will be the initiator
  376. // and one end will be the listener. Generally it is recommended that the
  377. // listener be the server. If the client is listening, network conditions
  378. // may lead to a state where the client has a phantom connection and it will
  379. // never find out since it doesn't initiate traffic. In this case, restart
  380. // the interface if there isn't traffic for a period
  381. property IsListener: Boolean read FIsListener write SetIsListener default DEFAULT_IS_LISTENER;
  382. // useful for application
  383. property OnConnect: TNotifyEvent read FOnConnect write FOnConnect;
  384. property OnDisconnect: TNotifyEvent read FOnDisconnect write FOnDisconnect;
  385. // this is called whenever OnConnect and OnDisconnect are called, and at other times, but only when server
  386. // it will be called after OnConnect and before OnDisconnect
  387. property OnConnCountChange: TIdHL7ConnCountEvent read FOnConnCountChange write FOnConnCountChange;
  388. // this is called when an unhandled exception is generated by the
  389. // hl7 object or the application. It allows the application to
  390. // construct a useful return error, log the exception, and drop the
  391. // connection if it wants
  392. property OnReceiveError: TReceiveErrorEvent read FOnReceiveError write FOnReceiveError;
  393. end;
  394. implementation
  395. uses
  396. {$IFDEF USE_VCL_POSIX}
  397. {$IFDEF OSX}
  398. Macapi.CoreServices,
  399. {$ENDIF}
  400. {$ENDIF}
  401. IdGlobalProtocols,
  402. IdResourceStringsProtocols;
  403. type
  404. IIdQueuedMessage = interface(IInterface)
  405. ['{CF62BBC6-784E-4B79-B58B-4930330EB173}']
  406. function GetMessage: String;
  407. function GetReply: String;
  408. procedure SetReply(const AValue: String);
  409. procedure SetEvent;
  410. procedure Wait;
  411. property Message: String read GetMessage;
  412. property Reply: String read GetReply write SetReply;
  413. end;
  414. TIdQueuedMessage = class(TIdInterfacedObject, IIdQueuedMessage)
  415. private
  416. FEvent: TIdLocalEvent;
  417. FMsg: String;
  418. FTimeOut: LongWord;
  419. FReply: String;
  420. public
  421. constructor Create(const AMsg: String; ATimeOut: LongWord);
  422. destructor Destroy; override;
  423. function GetMessage: String;
  424. function GetReply: String;
  425. procedure SetReply(const AValue: String);
  426. procedure SetEvent;
  427. procedure Wait;
  428. end;
  429. { TIdHL7KeepAlive }
  430. procedure TIdHL7KeepAlive.Assign(Source: TPersistent);
  431. var
  432. LSource: TIdHL7KeepAlive;
  433. begin
  434. if Source is TIdHl7KeepAlive then begin
  435. LSource := TIdHL7KeepAlive(Source);
  436. FUseKeepAlive := LSource.UseKeepAlive;
  437. FIdleTimeMS := LSource.IdleTimeMS;
  438. FIntervalMS := LSource.IntervalMS;
  439. end else begin
  440. inherited Assign(Source);
  441. end;
  442. end;
  443. { TIdQueuedMessage }
  444. constructor TIdQueuedMessage.Create(const AMsg: String; ATimeOut: LongWord);
  445. begin
  446. Assert(Length(AMsg) > 0, 'Attempt to queue an empty message'); {do not localize}
  447. Assert(ATimeout <> 0, 'Attempt to queue a message with no timeout'); {do not localize}
  448. inherited Create;
  449. FEvent := TIdLocalEvent.Create(False, False);
  450. FMsg := AMsg;
  451. FTimeOut := ATimeOut;
  452. end;
  453. destructor TIdQueuedMessage.Destroy;
  454. begin
  455. Assert(Assigned(Self));
  456. FreeAndNil(FEvent);
  457. inherited;
  458. end;
  459. function TIdQueuedMessage.GetMessage: String;
  460. begin
  461. Assert(Assigned(Self));
  462. Result := FMsg;
  463. end;
  464. function TIdQueuedMessage.GetReply: string;
  465. begin
  466. Assert(Assigned(Self));
  467. Result := FReply;
  468. end;
  469. procedure TIdQueuedMessage.SetReply(const AValue: String);
  470. begin
  471. Assert(Assigned(Self));
  472. FReply := AValue;
  473. end;
  474. procedure TIdQueuedMessage.SetEvent;
  475. begin
  476. Assert(Assigned(Self));
  477. Assert(Assigned(FEvent));
  478. FEvent.SetEvent;
  479. end;
  480. procedure TIdQueuedMessage.Wait;
  481. begin
  482. Assert(Assigned(Self));
  483. Assert(Assigned(FEvent));
  484. FEvent.WaitFor(FTimeOut);
  485. end;
  486. { EHL7CommunicationError }
  487. constructor EHL7CommunicationError.Create(AnInterfaceName, AMessage: String);
  488. begin
  489. //Assert(AInterfaceName <> '', 'Attempt to create an exception for an unnamed interface')
  490. //Assert(AMessage <> '', 'Attempt to create an exception with an empty message')
  491. // actually, we do not enforce either of these conditions, though they should both be true,
  492. // since we are already raising an exception
  493. FInterfaceName := AnInterfaceName;
  494. if FInterfaceName <> '' then
  495. begin
  496. inherited Create('[' + AnInterfaceName + '] ' + AMessage); {do not localize}
  497. end else begin
  498. inherited Create(AMessage);
  499. end;
  500. end;
  501. { TIdHL7 }
  502. {$IFDEF WORKAROUND_INLINE_CONSTRUCTORS}
  503. constructor TIdHL7.Create(AOwner: TComponent);
  504. begin
  505. inherited Create(AOwner);
  506. end;
  507. {$ENDIF}
  508. procedure TIdHL7.InitComponent;
  509. begin
  510. inherited;
  511. // partly redundant initialization of properties
  512. FKeepAlive := TIdHL7KeepAlive.Create;
  513. FIsListener := DEFAULT_IS_LISTENER;
  514. FCommunicationMode := DEFAULT_COMM_MODE;
  515. FTimeOut := DEFAULT_TIMEOUT;
  516. FReconnectDelay := DEFAULT_RECONNECT_DELAY;
  517. FReceiveTimeout := DEFAULT_RECEIVE_TIMEOUT;
  518. FConnectionLimit := DEFAULT_CONN_LIMIT;
  519. FIPMask := NULL_IP;
  520. FIPRestriction := NULL_IP;
  521. FAddress := DEFAULT_ADDRESS;
  522. FPort := DEFAULT_PORT;
  523. FOnReceiveMessage := nil;
  524. FOnConnect := nil;
  525. FOnDisconnect := nil;
  526. FObject := nil;
  527. // initialise status
  528. FStatus := IsStopped;
  529. FStatusDesc := RSHL7StatusStopped;
  530. // build internal infrastructure
  531. FLock := TIdCriticalSection.Create;
  532. FServer := nil;
  533. FServerConn := nil;
  534. FClientThread := nil;
  535. FClient := nil;
  536. FMsgQueue := TInterfaceList.Create;
  537. FHndMsgQueue := TInterfaceList.Create;
  538. FWaitingForAnswer := False;
  539. FMsgReply := '';
  540. FReplyResponse := srNone;
  541. FWaitEvent := TIdLocalEvent.Create(False, False);
  542. FServerConnections := TObjectList.Create;
  543. FServerConnections.OwnsObjects := False;
  544. FDefStringEncoding := IndyTextEncoding_UTF8;
  545. {$IFDEF STRING_IS_ANSI}
  546. FDefAnsiEncoding := IndyTextEncoding_OSDefault;
  547. {$ENDIF}
  548. end;
  549. destructor TIdHL7.Destroy;
  550. begin
  551. Assert(Assigned(Self));
  552. try
  553. if Going then
  554. begin
  555. Stop;
  556. end;
  557. finally
  558. FreeAndNil(FServerConnections);
  559. FreeAndNil(FKeepAlive);
  560. FreeAndNil(FMsgQueue);
  561. FreeAndNil(FHndMsgQueue);
  562. FreeAndNil(FWaitEvent);
  563. FreeAndNil(FLock);
  564. inherited;
  565. end;
  566. end;
  567. {==========================================================
  568. Property Servers
  569. ==========================================================}
  570. procedure TIdHL7.SetDefStringEncoding(const AValue: IIdTextEncoding);
  571. var
  572. LEncoding: IIdTextEncoding;
  573. begin
  574. Assert(Assigned(Self));
  575. if Going then
  576. begin
  577. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['DefStringEncoding'])); {do not localize}
  578. end;
  579. if FDefStringEncoding <> AValue then
  580. begin
  581. LEncoding := AValue;
  582. EnsureEncoding(LEncoding, encUTF8);
  583. FDefStringEncoding := LEncoding;
  584. end;
  585. end;
  586. {$IFDEF STRING_IS_ANSI}
  587. procedure TIdHL7.SetDefAnsiEncoding(const AValue: IIdTextEncoding);
  588. var
  589. LEncoding: IIdTextEncoding;
  590. begin
  591. Assert(Assigned(Self));
  592. if Going then
  593. begin
  594. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['DefAnsiEncoding'])); {do not localize}
  595. end;
  596. if FDefAnsiEncoding <> AValue then
  597. begin
  598. LEncoding := AValue;
  599. EnsureEncoding(LEncoding, encOSDefault);
  600. FDefAnsiEncoding := LEncoding;
  601. end;
  602. end;
  603. {$ENDIF}
  604. procedure TIdHL7.SetAddress(const AValue: String);
  605. begin
  606. Assert(Assigned(Self));
  607. // we don't make any assertions about AValue - will be '' if we are a server
  608. if Going then
  609. begin
  610. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['Address'])); {do not localize??}
  611. end;
  612. FAddress := AValue;
  613. end;
  614. procedure TIdHL7.SetConnectionLimit(const AValue: Word);
  615. begin
  616. Assert(Assigned(Self));
  617. // no restrictions on AValue
  618. if Going then
  619. begin
  620. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['ConnectionLimit'])); {do not localize??}
  621. end;
  622. FConnectionLimit := AValue;
  623. end;
  624. procedure TIdHL7.SetIPMask(const AValue: String);
  625. begin
  626. Assert(Assigned(Self));
  627. // TODO: enforce that AValue is a valid Subnet mask
  628. if Going then
  629. begin
  630. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['IP Mask'])); {do not localize??}
  631. end;
  632. FIPMaskVal := IPv4ToUInt32(AValue);
  633. FIPMask := AValue;
  634. end;
  635. procedure TIdHL7.SetIPRestriction(const AValue: string);
  636. begin
  637. Assert(Assigned(Self));
  638. // to do: enforce that AValue is a valid IP address range
  639. if Going then
  640. begin
  641. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['IP Restriction'])); {do not localize??}
  642. end;
  643. FIPRestrictionVal := IPv4ToUInt32(AValue);
  644. FIPRestriction := AValue;
  645. end;
  646. procedure TIdHL7.SetPort(const AValue: Word);
  647. begin
  648. Assert(Assigned(Self));
  649. Assert(AValue <> 0, 'Attempt to use Port 0 for HL7 Communications'); {do not localize}
  650. if Going then
  651. begin
  652. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['Port'])); {do not localize}
  653. end;
  654. FPort := AValue;
  655. end;
  656. procedure TIdHL7.SetReconnectDelay(const AValue: LongWord);
  657. begin
  658. Assert(Assigned(Self));
  659. // any value for AValue is accepted, although this may not make sense
  660. if Going then
  661. begin
  662. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['Reconnect Delay'])); {do not localize}
  663. end;
  664. FReconnectDelay := AValue;
  665. end;
  666. procedure TIdHL7.SetTimeOut(const AValue: UInt32);
  667. begin
  668. Assert(Assigned(Self));
  669. Assert(AValue > 0, 'Attempt to configure TIdHL7 with a TimeOut of 0'); {do not localize}
  670. // we don't function at all if timeout is 0, though there are circumstances where it's not relevent
  671. if Going then
  672. begin
  673. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['Time Out'])); {do not localize??}
  674. end;
  675. FTimeOut := AValue;
  676. end;
  677. procedure TIdHL7.SetCommunicationMode(const AValue: THL7CommunicationMode);
  678. begin
  679. Assert(Assigned(Self));
  680. Assert((AValue >= Low(THL7CommunicationMode)) and (AValue <= High(THL7CommunicationMode)), 'Value for TIdHL7.CommunicationMode not in range'); {do not localize}
  681. // only could arise if someone is typecasting?
  682. if Going then
  683. begin
  684. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['Communication Mode'])); {do not localize}
  685. end;
  686. FCommunicationMode := AValue;
  687. end;
  688. procedure TIdHL7.SetIsListener(const AValue: Boolean);
  689. begin
  690. Assert(Assigned(Self));
  691. // AValue isn't checked
  692. if Going then
  693. begin
  694. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['IsListener'])); {do not localize}
  695. end;
  696. FIsListener := AValue;
  697. end;
  698. function TIdHL7.GetStatus: TIdHL7Status;
  699. begin
  700. Assert(Assigned(Self));
  701. Assert(Assigned(FLock));
  702. FLock.Enter;
  703. try
  704. Result := FStatus;
  705. finally
  706. FLock.Leave;
  707. end;
  708. end;
  709. function TIdHL7.Connected: boolean;
  710. begin
  711. Assert(Assigned(Self));
  712. Assert(Assigned(FLock));
  713. FLock.Enter;
  714. try
  715. Result := (FStatus = IsConnected);
  716. finally
  717. FLock.Leave;
  718. end;
  719. end;
  720. function TIdHL7.GetStatusDesc: String;
  721. begin
  722. Assert(Assigned(Self));
  723. Assert(Assigned(FLock));
  724. FLock.Enter;
  725. try
  726. Result := FStatusDesc;
  727. finally
  728. FLock.Leave;
  729. end;
  730. end;
  731. procedure TIdHL7.InternalSetStatus(const AStatus: TIdHL7Status; ADesc: String);
  732. begin
  733. Assert(Assigned(Self));
  734. Assert((AStatus >= Low(TIdHL7Status)) and (AStatus <= High(TIdHL7Status)), 'Value for TIdHL7.CommunicationMode not in range'); {do not localize}
  735. // ADesc is allowed to be anything at all
  736. Assert(Assigned(FLock));
  737. FLock.Enter;
  738. try
  739. FStatus := AStatus;
  740. FStatusDesc := ADesc;
  741. finally
  742. FLock.Leave;
  743. end;
  744. end;
  745. {==========================================================
  746. Application Control
  747. ==========================================================}
  748. procedure TIdHL7.Start;
  749. var
  750. LStatus: TIdHL7Status;
  751. begin
  752. Assert(Assigned(Self));
  753. LStatus := GetStatus;
  754. if LStatus = IsUnusable then
  755. begin
  756. raise EHL7CommunicationError.Create(Name, RSHL7NotFailedToStop);
  757. end;
  758. if LStatus <> IsStopped then
  759. begin
  760. raise EHL7CommunicationError.Create(Name, RSHL7AlreadyStarted);
  761. end;
  762. if FCommunicationMode = cmUnknown then
  763. begin
  764. raise EHL7CommunicationError.Create(Name, RSHL7ModeNotSet);
  765. end;
  766. if FCommunicationMode = cmAsynchronous then
  767. begin
  768. if not Assigned(FOnMessageArrive) then
  769. begin
  770. raise EHL7CommunicationError.Create(Name, RSHL7NoAsynEvent);
  771. end;
  772. end;
  773. if (FCommunicationMode = cmSynchronous) and IsListener then
  774. begin
  775. if not Assigned(FOnReceiveMessage) then
  776. begin
  777. raise EHL7CommunicationError.Create(Name, RSHL7NoSynEvent);
  778. end;
  779. end;
  780. FIsServer := (FAddress = '');
  781. FPreStopped := False;
  782. FWaitingForAnswer := False;
  783. if FIsServer then
  784. begin
  785. StartServer;
  786. end else begin
  787. StartClient;
  788. end;
  789. end;
  790. procedure TIdHL7.PreStop;
  791. procedure JoltList(list: TInterfaceList);
  792. var
  793. i: Integer;
  794. begin
  795. for i := 0 to list.Count - 1 do
  796. begin
  797. IIdQueuedMessage(list[i]).SetEvent;
  798. end;
  799. end;
  800. begin
  801. Assert(Assigned(Self));
  802. if FCommunicationMode = cmSingleThread then
  803. begin
  804. Assert(Assigned(FLock));
  805. Assert(Assigned(FMsgQueue));
  806. Assert(Assigned(FHndMsgQueue));
  807. FLock.Enter;
  808. try
  809. JoltList(FMsgQueue);
  810. JoltList(FHndMsgQueue);
  811. finally
  812. FLock.Leave;
  813. end;
  814. end
  815. else if FCommunicationMode = cmSynchronous then
  816. begin
  817. Assert(Assigned(FWaitEvent));
  818. FWaitEvent.SetEvent;
  819. end;
  820. FPreStopped := True;
  821. end;
  822. procedure TIdHL7.Stop;
  823. begin
  824. Assert(Assigned(Self));
  825. if not Going then
  826. begin
  827. raise EHL7CommunicationError.Create(Name, RSHL7AlreadyStopped);
  828. end;
  829. if not FPreStopped then
  830. begin
  831. PreStop;
  832. IndySleep(10); // give other threads a chance to clean up
  833. end;
  834. if FIsServer then begin
  835. StopServer;
  836. end else begin
  837. StopClient;
  838. end;
  839. end;
  840. {==========================================================
  841. Server Connection Maintainance
  842. ==========================================================}
  843. procedure TIdHL7.EnforceWaitReplyTimeout;
  844. begin
  845. Stop;
  846. Start;
  847. end;
  848. function TIdHL7.Going: Boolean;
  849. var
  850. LStatus: TIdHL7Status;
  851. begin
  852. Assert(Assigned(Self));
  853. LStatus := GetStatus;
  854. Result := (LStatus <> IsStopped) and (LStatus <> IsUnusable);
  855. end;
  856. procedure TIdHL7.WaitForConnection(AMaxLength: UInt32);
  857. var
  858. LStartTime: TIdTicks;
  859. begin
  860. LStartTime := Ticks64;
  861. while (not Connected) and (GetElapsedTicks(LStartTime) < AMaxLength) do begin
  862. IndySleep(50);
  863. end;
  864. end;
  865. procedure TIdHL7.CheckSynchronousSendResult(AResult: TSendResponse; const AMsg: String);
  866. begin
  867. case AResult of
  868. srNone:
  869. raise EHL7CommunicationError.Create(Name, RSHL7ErrInternalsrNone);
  870. srError:
  871. raise EHL7CommunicationError.Create(Name, AMsg);
  872. srNoConnection:
  873. raise EHL7CommunicationError.Create(Name, RSHL7ErrNotConn);
  874. srSent:
  875. // cause this should only be returned asynchronously
  876. raise EHL7CommunicationError.Create(Name, RSHL7ErrInternalsrSent);
  877. srOK: ; // all ok
  878. srTimeout:
  879. raise EHL7CommunicationError.Create(Name, RSHL7ErrNoResponse);
  880. else
  881. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7ErrInternalUnknownVal, [Ord(AResult)]));
  882. end;
  883. end;
  884. procedure TIdHL7.SetConnectionTimeout(const AValue: UInt32);
  885. begin
  886. Assert(Assigned(Self));
  887. // any value for AValue is accepted, although this may not make sense
  888. if Going then
  889. begin
  890. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['Connection Timeout'])); {do not localize??}
  891. end;
  892. FConnectionTimeout := AValue;
  893. end;
  894. procedure TIdHL7.ReConnectFromTimeout;
  895. var
  896. iLoop : Integer;
  897. begin
  898. Assert(Assigned(Self));
  899. Assert(not FIsServer, 'Cannot try to reconnect from a timeout if acting as a server'); {do not localize}
  900. StartClient;
  901. IndySleep(50);
  902. iLoop := 0;
  903. while (not Connected) and (iLoop < 100) and (not FPreStopped) do
  904. begin
  905. IndySleep(100);
  906. Inc(iLoop);
  907. end;
  908. // TODO: raise an error if not connected or prestopped?
  909. end;
  910. procedure TIdHL7.SetKeepAlive(const AValue: TIdHL7KeepAlive);
  911. begin
  912. if Going then
  913. begin
  914. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWhileWorking, ['KeepAlive'])); {do not localize??}
  915. end;
  916. FKeepAlive.Assign(AValue);
  917. end;
  918. function TIdHL7.HasClientConnection: Boolean;
  919. begin
  920. Result := Assigned(FClientThread);
  921. end;
  922. procedure TIdHL7.Disconnect;
  923. var
  924. i: Integer;
  925. begin
  926. if FIsServer then
  927. begin
  928. FLock.Enter;
  929. try
  930. for i := 0 to FServerConnections.Count - 1 do begin
  931. TIdContext(FServerConnections[i]).Connection.Disconnect;
  932. end;
  933. finally
  934. FLock.Leave;
  935. end;
  936. end
  937. else if Assigned(FClientThread) then begin
  938. FClientThread.FClient.Disconnect;
  939. end;
  940. end;
  941. procedure TIdHL7.CheckServerParameters;
  942. begin
  943. Assert(Assigned(Self));
  944. if (FCommunicationMode = cmAsynchronous) or (not FIsListener) then
  945. begin
  946. FConnectionLimit := 1;
  947. end;
  948. if (FPort < 1) then // though we have already ensured that this cannot happen
  949. begin
  950. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7InvalidPort, [FPort]));
  951. end;
  952. end;
  953. procedure TIdHL7.StartServer;
  954. var
  955. i: Integer;
  956. begin
  957. Assert(Assigned(Self));
  958. CheckServerParameters;
  959. FServer := TIdTCPServer.Create(nil);
  960. try
  961. FServer.DefaultPort := FPort;
  962. FServer.OnConnect := ServerConnect;
  963. FServer.OnExecute := ServerExecute;
  964. FServer.OnDisconnect := ServerDisconnect;
  965. // RLebeau: this unit does not currently support restriction of IPv6 clients, so
  966. // adding an explicit IPv4 binding to prevent TIdTCPServer from creating an implicit
  967. // IPv6 binding on systems that allow dual IPv4/IPv6 bindings on the same ip/port...
  968. FServer.Bindings.Add.IPVersion := Id_IPv4; // TODO: support IPv6 clients?
  969. FServer.Active := True;
  970. if FKeepAlive.UseKeepAlive then
  971. begin
  972. for i := 0 to FServer.Bindings.Count - 1 do begin
  973. FServer.Bindings[i].SetKeepAliveValues(True, FKeepAlive.IdleTimeMS, FKeepAlive.IntervalMS);
  974. end;
  975. end;
  976. InternalSetStatus(IsNotConnected, RSHL7StatusNotConnected);
  977. except
  978. on e: Exception do
  979. begin
  980. InternalSetStatus(IsStopped, IndyFormat(RSHL7StatusFailedToStart, [e.Message]));
  981. FreeAndNil(FServer);
  982. raise;
  983. end;
  984. end;
  985. end;
  986. procedure TIdHL7.StopServer;
  987. begin
  988. Assert(Assigned(Self));
  989. try
  990. FServer.Active := False;
  991. FreeAndNil(FServer);
  992. InternalSetStatus(IsStopped, RSHL7StatusStopped);
  993. except
  994. on e: Exception do
  995. begin
  996. // somewhat arbitrary decision: if for some reason we fail to shutdown,
  997. // we will stubbornly refuse to work again.
  998. InternalSetStatus(IsUnusable, IndyFormat(RSHL7StatusFailedToStop, [e.Message]));
  999. FServer := nil; // Note: potential memory leak!
  1000. raise;
  1001. end;
  1002. end;
  1003. end;
  1004. procedure TIdHL7.ServerConnect(AContext: TIdContext);
  1005. var
  1006. LNotify: Boolean;
  1007. LConnCount: Integer;
  1008. LValid: Boolean;
  1009. LIPStr: String;
  1010. LIPVal: UInt32;
  1011. begin
  1012. Assert(Assigned(Self));
  1013. Assert(Assigned(AContext));
  1014. Assert(Assigned(AContext.Binding));
  1015. Assert(Assigned(FLock));
  1016. LConnCount := 0;
  1017. LIPStr := AContext.Binding.PeerIP;
  1018. LIPVal := IPv4ToUInt32(LIPStr);
  1019. if ((LIPVal xor FIPRestrictionVal) and FIPMaskVal) <> 0 then
  1020. begin
  1021. raise Exception.Create('Denied'); {do not localize}
  1022. end;
  1023. FLock.Enter;
  1024. try
  1025. LConnCount := FServerConnections.Count;
  1026. LNotify := (LConnCount = 0);
  1027. LValid := (LConnCount < FConnectionLimit);
  1028. if LValid then
  1029. begin
  1030. if (LConnCount = 0) then
  1031. begin
  1032. FServerConn := AContext.Connection;
  1033. end else begin
  1034. FServerConn := nil; // RLebeau: why?
  1035. end;
  1036. FServerConnections.Add(AContext);
  1037. Inc(LConnCount);
  1038. if LNotify then
  1039. begin
  1040. InternalSetStatus(IsConnected, RSHL7StatusConnected);
  1041. end;
  1042. AContext.Connection.IOHandler.ReadTimeout := FReceiveTimeout;
  1043. end;
  1044. finally
  1045. FLock.Leave;
  1046. end;
  1047. if LValid then
  1048. begin
  1049. if LNotify and Assigned(FOnConnect) then begin
  1050. FOnConnect(self);
  1051. end;
  1052. if Assigned(FOnConnCountChange) and (FConnectionLimit <> 1) then begin
  1053. FOnConnCountChange(Self, LConnCount);
  1054. end;
  1055. end else begin
  1056. // Thread exceeds connection limit
  1057. // it would be better to stop getting here in the case of an invalid connection
  1058. // cause here we drop it - nasty for the client. To be investigated later
  1059. AContext.Connection.Disconnect;
  1060. end;
  1061. end;
  1062. procedure TIdHL7.ServerDisconnect(AContext: TIdContext);
  1063. var
  1064. LNotify: Boolean;
  1065. LConnCount: Integer;
  1066. begin
  1067. Assert(Assigned(Self));
  1068. Assert(Assigned(AContext));
  1069. Assert(Assigned(FLock));
  1070. FLock.Enter;
  1071. try
  1072. FServerConnections.Remove(AContext);
  1073. LConnCount := FServerConnections.Count;
  1074. LNotify := (LConnCount = 0);
  1075. if AContext.Connection = FServerConn then
  1076. begin
  1077. FServerConn := nil;
  1078. end;
  1079. if LNotify then
  1080. begin
  1081. InternalSetStatus(IsNotConnected, RSHL7StatusNotConnected);
  1082. end;
  1083. finally
  1084. FLock.Leave;
  1085. end;
  1086. //Note events outside of critical section as they are expected to have critical thread save logic build into them
  1087. if Assigned(FOnConnCountChange) and (FConnectionLimit <> 1) then begin
  1088. FOnConnCountChange(Self, LConnCount); //Current causes Thread to freeze if called event does something like write to memobox even if in a critical section
  1089. end;
  1090. if LNotify and Assigned(FOnDisconnect) then begin
  1091. FOnDisconnect(Self); //Current causes Thread to freeze if called event does something like write to memobox even if in a critical section
  1092. end;
  1093. end;
  1094. procedure TIdHL7.ServerExecute(AContext: TIdContext);
  1095. var
  1096. LBuffer: TIdBytes;
  1097. begin
  1098. Assert(Assigned(Self));
  1099. Assert(Assigned(AContext));
  1100. FIsServerExecuting := True;
  1101. try
  1102. // 1. prompt the network for content.
  1103. while Assigned(AContext.Connection.IOHandler) do
  1104. begin
  1105. AContext.Connection.IOHandler.ReadBytes(LBuffer, -1, True);
  1106. HandleIncoming(LBuffer, AContext.Connection);
  1107. end;
  1108. except
  1109. try
  1110. // well, there was some network error. We aren't sure what it
  1111. // was, and it doesn't matter for this layer. we're just going
  1112. // to make sure that we start again.
  1113. // to review: what happens to the error messages?
  1114. AContext.Connection.Disconnect;
  1115. except
  1116. end;
  1117. end;
  1118. FIsServerExecuting := False;
  1119. end;
  1120. procedure TIdHL7.DropServerConnection;
  1121. begin
  1122. Assert(Assigned(Self));
  1123. Assert(Assigned(FLock));
  1124. FLock.Enter;
  1125. try
  1126. if Assigned(FServerConn) then begin
  1127. FServerConn.Disconnect;
  1128. end;
  1129. finally
  1130. FLock.Leave;
  1131. end;
  1132. end;
  1133. {==========================================================
  1134. Client Connection Maintainance
  1135. ==========================================================}
  1136. procedure TIdHL7.CheckClientParameters;
  1137. begin
  1138. Assert(Assigned(Self));
  1139. if (FPort < 1) then
  1140. begin
  1141. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7InvalidPort, [FPort]));
  1142. end;
  1143. end;
  1144. procedure TIdHL7.StartClient;
  1145. begin
  1146. Assert(Assigned(Self));
  1147. CheckClientParameters;
  1148. FClientThread := TIdHL7ClientThread.Create(Self);
  1149. InternalSetStatus(isConnecting, RSHL7StatusConnecting);
  1150. end;
  1151. procedure TIdHL7.StopClient;
  1152. var
  1153. LFinished: Boolean;
  1154. LStartTime: TIdTicks;
  1155. begin
  1156. Assert(Assigned(Self));
  1157. Assert(Assigned(FLock));
  1158. FLock.Enter;
  1159. try
  1160. if Assigned(FClientThread) then
  1161. begin
  1162. FClientThread.Terminate;
  1163. FClientThread.FClient.Disconnect;
  1164. FClientThread.FCloseEvent.SetEvent;
  1165. end else begin
  1166. InternalSetStatus(isStopped, 'Stopped'); {do not localize}
  1167. end;
  1168. finally
  1169. FLock.Leave;
  1170. end;
  1171. LStartTime := Ticks64;
  1172. repeat
  1173. LFinished := (GetStatus = IsStopped);
  1174. if not LFinished then begin
  1175. IndySleep(10);
  1176. end;
  1177. until LFinished or (GetElapsedTicks(LStartTime) > WAIT_STOP);
  1178. if GetStatus <> IsStopped then
  1179. begin
  1180. // for some reason the client failed to shutdown. We will stubbornly refuse to work again
  1181. InternalSetStatus(IsUnusable, IndyFormat(RSHL7StatusFailedToStop, [RSHL7ClientThreadNotStopped]));
  1182. end;
  1183. end;
  1184. procedure TIdHL7.DropClientConnection;
  1185. begin
  1186. Assert(Assigned(Self));
  1187. Assert(Assigned(FLock));
  1188. FLock.Enter;
  1189. try
  1190. if Assigned(FClientThread) then begin
  1191. FClientThread.FClient.Disconnect;
  1192. end else begin
  1193. // This may happen validly because both ends are trying to drop the connection simultaineously
  1194. end;
  1195. finally
  1196. FLock.Leave;
  1197. end;
  1198. end;
  1199. { TIdHL7ClientThread }
  1200. constructor TIdHL7ClientThread.Create(AOwner: TIdHL7);
  1201. begin
  1202. Assert(Assigned(AOwner));
  1203. FOwner := AOwner;
  1204. FCloseEvent := TIdLocalEvent.Create(True, False);
  1205. FClient := TIdTCPClient.Create(nil);
  1206. FClient.Host := AOwner.Address;
  1207. FClient.Port := AOwner.Port;
  1208. FClient.ReadTimeout := AOwner.ReceiveTimeout;
  1209. FClient.UseNagle := True;
  1210. inherited Create(False);
  1211. FreeOnTerminate := True;
  1212. end;
  1213. destructor TIdHL7ClientThread.Destroy;
  1214. begin
  1215. Assert(Assigned(Self));
  1216. Assert(Assigned(FOwner));
  1217. Assert(Assigned(FOwner.FLock));
  1218. try
  1219. FOwner.FLock.Enter;
  1220. try
  1221. FOwner.FClientThread := nil;
  1222. if not TimedOut then begin
  1223. FOwner.InternalSetStatus(isStopped, RSHL7StatusStopped);
  1224. end;
  1225. finally
  1226. FOwner.FLock.Leave;
  1227. end;
  1228. except
  1229. // it's really vaguely possible that the owner
  1230. // may be dead before we are. If that is the case, we blow up here.
  1231. // who cares.
  1232. end;
  1233. FreeAndNil(FCloseEvent);
  1234. FreeAndNil(FClient);
  1235. inherited;
  1236. end;
  1237. procedure TIdHL7ClientThread.PollStack;
  1238. var
  1239. LBuffer: TIdBytes;
  1240. begin
  1241. Assert(Assigned(Self));
  1242. repeat
  1243. // we don't send here - we just poll the stack for content
  1244. // if the application wants to terminate us at this point,
  1245. // then it will disconnect the socket and we will get thrown
  1246. // out
  1247. // we really don't care at all whether the disconnect was clean or ugly
  1248. // but we do need to suppress exceptions that come from
  1249. // indy otherwise the client thread will terminate
  1250. try
  1251. while Assigned(FClient.IOHandler) do
  1252. begin
  1253. FClient.IOHandler.ReadBytes(LBuffer, -1, True);
  1254. FOwner.HandleIncoming(LBuffer, FClient);
  1255. end;
  1256. except
  1257. try
  1258. // well, there was some network error. We aren't sure what it
  1259. // was, and it doesn't matter for this layer. we're just going
  1260. // to make sure that we start again.
  1261. // to review: what happens to the error messages?
  1262. FClient.Disconnect;
  1263. except
  1264. end;
  1265. end;
  1266. until Terminated or (not FClient.Connected);
  1267. end;
  1268. const
  1269. SECOND_LENGTH = 1000;
  1270. MINUTE_LENGTH = SECOND_LENGTH * 60;
  1271. HOUR_LENGTH = MINUTE_LENGTH * 60;
  1272. DAY_LENGTH = HOUR_LENGTH * 24;
  1273. function DescribePeriod(Period: LongWord): String;
  1274. begin
  1275. if Period < SECOND_LENGTH then begin
  1276. Result := IntToStr(Period) + 'ms' {do not localize}
  1277. end
  1278. else if Period < (180 * SECOND_LENGTH) then begin
  1279. Result := IntToStr(trunc(Period / SECOND_LENGTH)) + 'sec' {do not localize}
  1280. end
  1281. else if Period < (180 * MINUTE_LENGTH) then begin
  1282. Result := IntToStr(trunc(Period / MINUTE_LENGTH)) + 'min' {do not localize}
  1283. end
  1284. else if Period < (72 * HOUR_LENGTH) then begin
  1285. Result := IntToStr(trunc(Period / HOUR_LENGTH)) + 'hr' {do not localize}
  1286. end else begin
  1287. Result := IntToStr(trunc(Period / DAY_LENGTH)) + ' days'; {do not localize}
  1288. end;
  1289. end;
  1290. procedure TIdHL7ClientThread.Execute;
  1291. begin
  1292. Assert(Assigned(Self));
  1293. try
  1294. repeat
  1295. // try to connect. Try indefinitely but wait Owner.FReconnectDelay
  1296. // between attempts. Problems: how long does Connect take?
  1297. repeat
  1298. FOwner.InternalSetStatus(IsConnecting, rsHL7StatusConnecting);
  1299. try
  1300. FClient.Connect;
  1301. Break;
  1302. except
  1303. on e: Exception do
  1304. begin
  1305. //now we can take more liberties with the time and date output because it's only
  1306. //for human consumption (probably in a log
  1307. FOwner.InternalSetStatus(IsWaitReconnect, IndyFormat(rsHL7StatusReConnect, [DescribePeriod(FOwner.FReconnectDelay), e.Message]));
  1308. end;
  1309. end;
  1310. if Terminated then Break;
  1311. // TODO: run this in a smaller loop checking Terminated on each iteration,
  1312. // or hook up this event to TThread.TerminatedSet()...
  1313. FCloseEvent.WaitFor(FOwner.FReconnectDelay);
  1314. until Terminated;
  1315. if Terminated then begin
  1316. Exit;
  1317. end;
  1318. if FOwner.FKeepAlive.UseKeepAlive then begin
  1319. FClient.Socket.Binding.SetKeepAliveValues(True, FOwner.FKeepAlive.IdleTimeMS, FOwner.FKeepAlive.IntervalMS);
  1320. end;
  1321. FLastTraffic := Ticks64;
  1322. FOwner.FLock.Enter;
  1323. try
  1324. FOwner.FClient := FClient;
  1325. FOwner.InternalSetStatus(IsConnected, rsHL7StatusConnected);
  1326. finally
  1327. FOwner.FLock.Leave;
  1328. end;
  1329. if Assigned(FOwner.FOnConnect) then begin
  1330. FOwner.FOnConnect(FOwner);
  1331. end;
  1332. try
  1333. PollStack;
  1334. finally
  1335. FOwner.FLock.Enter;
  1336. try
  1337. FOwner.FClient := nil;
  1338. if TimedOut then begin
  1339. FOwner.InternalSetStatus(isTimedOut, RSHL7StatusTimedout);
  1340. end else begin
  1341. FOwner.InternalSetStatus(IsNotConnected, RSHL7StatusNotConnected);
  1342. end;
  1343. finally
  1344. FOwner.FLock.Leave;
  1345. end;
  1346. if Assigned(FOwner.FOnDisconnect) then begin
  1347. FOwner.FOnDisconnect(FOwner);
  1348. end;
  1349. end;
  1350. if TimedOut then begin
  1351. FClient.Disconnect;
  1352. end
  1353. else if not Terminated then
  1354. begin
  1355. // we got disconnected. ReconnectDelay applies.
  1356. FOwner.InternalSetStatus(IsWaitReconnect, IndyFormat(rsHL7StatusReConnect, [DescribePeriod(FOwner.FReconnectDelay), 'Disconnected'])); {do not localize}
  1357. // TODO: run this in a smaller loop checking Terminated on each iteration,
  1358. // or hook up this event to TThread.TerminatedSet()...
  1359. FCloseEvent.WaitFor(FOwner.FReconnectDelay);
  1360. end;
  1361. until Terminated or (not FOwner.IsListener and TimedOut);
  1362. except
  1363. on e: Exception do
  1364. begin
  1365. // presumably some comms or indy related exception
  1366. // there's not really any good place to put this????
  1367. end;
  1368. end;
  1369. end;
  1370. function TIdHL7ClientThread.TimedOut: boolean;
  1371. begin
  1372. Result := (FOwner.FConnectionTimeout > 0) and (GetElapsedTicks(FLastTraffic) > FOwner.FConnectionTimeout);
  1373. end;
  1374. {==========================================================
  1375. Internal process management
  1376. ==========================================================}
  1377. function EncodeHL7Message(const AMsg: String; AByteEncoding: IIdTextEncoding
  1378. {$IFDEF STRING_IS_ANSI}; AAnsiEncoding: IIdTextEncoding{$ENDIF}
  1379. ): TIdBytes;
  1380. var
  1381. LMsgLen, LIndex: Integer;
  1382. {$IFDEF STRING_IS_ANSI}
  1383. LTemp: TIdUnicodeString;
  1384. {$ENDIF}
  1385. begin
  1386. {$IFDEF STRING_IS_ANSI}
  1387. if AMsg <> '' then begin
  1388. LTemp := AAnsiEncoding.GetString(
  1389. {$IFNDEF VCL_6_OR_ABOVE}
  1390. // RLebeau: for some reason, Delphi 5 causes a "There is no overloaded
  1391. // version of 'GetString' that can be called with these arguments" compiler
  1392. // error if the PByte type-cast is used, even though GetString() actually
  1393. // expects a PByte as input. Must be a compiler bug, as it compiles fine
  1394. // in Delphi 6. So, converting to TIdBytes until I find a better solution...
  1395. RawToBytes(PAnsiChar(AMsg)^, Length(AMsg))
  1396. {$ELSE}
  1397. PByte(PAnsiChar(AMsg)), Length(AMsg)
  1398. {$ENDIF}
  1399. );
  1400. end;
  1401. LMsgLen := AByteEncoding.GetByteCount(LTemp);
  1402. {$ELSE}
  1403. LMsgLen := AByteEncoding.GetByteCount(AMsg);
  1404. {$ENDIF}
  1405. SetLength(Result, Length(MSG_START) + LMsgLen + Length(MSG_END));
  1406. LIndex := 0;
  1407. CopyTIdByteArray(MSG_START, 0, Result, LIndex, Length(MSG_START));
  1408. Inc(LIndex, Length(MSG_START));
  1409. AByteEncoding.GetBytes(
  1410. {$IFDEF STRING_IS_ANSI}LTemp{$ELSE}AMsg{$ENDIF},
  1411. 1, Length({$IFDEF STRING_IS_ANSI}LTemp{$ELSE}AMsg{$ENDIF}),
  1412. Result, LIndex
  1413. );
  1414. Inc(LIndex, LMsgLen);
  1415. CopyTIdByteArray(MSG_END, 0, Result, LIndex, Length(MSG_END));
  1416. end;
  1417. procedure TIdHL7.HandleIncoming(var VBuffer: TIdBytes; AConnection: TIdTCPConnection);
  1418. var
  1419. LStart, LEnd: Integer;
  1420. LMsg, LReply: String;
  1421. LBytes: TIdBytes;
  1422. {$IFDEF STRING_IS_ANSI}
  1423. LTemp: TIdUnicodeString;
  1424. {$ENDIF}
  1425. function FindBytes(const ABytesToSearch: TIdBytes; const ABytesToFind: array of Byte; AStart: Integer): Integer;
  1426. var
  1427. I: Integer;
  1428. LBytesLen, LFindLen: Integer;
  1429. LMatches: Boolean;
  1430. begin
  1431. LBytesLen := Length(ABytesToSearch);
  1432. LFindLen := Length(ABytesToFind);
  1433. while (AStart + LFindLen) <= LBytesLen do
  1434. begin
  1435. Result := ByteIndex(ABytesToFind[0], ABytesToSearch, AStart);
  1436. if Result = -1 then Exit;
  1437. LMatches := True;
  1438. for I := 1 to High(ABytesToFind) do
  1439. begin
  1440. if ABytesToSearch[Result + I] <> ABytesToFind[I] then
  1441. begin
  1442. LMatches := False;
  1443. Break;
  1444. end;
  1445. end;
  1446. if LMatches then Exit;
  1447. Inc(AStart);
  1448. end;
  1449. Result := -1;
  1450. end;
  1451. begin
  1452. Assert(Assigned(Self));
  1453. Assert(Length(VBuffer) > 0, 'Attempt to handle an empty buffer'); {do not localize}
  1454. Assert(Assigned(AConnection));
  1455. try
  1456. // process any messages in the buffer (may get more than one per packet)
  1457. repeat
  1458. LStart := FindBytes(VBuffer, MSG_START, 0);
  1459. if LStart >= 0 then begin
  1460. Inc(LStart, Length(MSG_START));
  1461. LEnd := FindBytes(VBuffer, MSG_END, LStart);
  1462. end else begin
  1463. LEnd := FindBytes(VBuffer, MSG_END, 0);
  1464. end;
  1465. if (LStart >= 0) and (LEnd >= 0) then
  1466. begin
  1467. {$IFDEF STRING_IS_ANSI}
  1468. LTemp := FDefStringEncoding.GetString(VBuffer, LStart, LEnd - LStart);
  1469. LBytes := FDefAnsiEncoding.GetBytes(LTemp);
  1470. SetString(LMsg, PAnsiChar(LBytes), Length(LBytes));
  1471. {$ELSE}
  1472. LMsg := FDefStringEncoding.GetString(VBuffer, LStart, LEnd - LStart);
  1473. {$ENDIF}
  1474. if HandleMessage(LMsg, AConnection, LReply) then
  1475. begin
  1476. if Length(LReply) > 0 then
  1477. begin
  1478. LBytes := EncodeHL7Message(LReply, FDefStringEncoding
  1479. {$IFDEF STRING_IS_ANSI}, FDefAnsiEncoding{$ENDIF}
  1480. );
  1481. AConnection.IOHandler.Write(LBytes);
  1482. end;
  1483. end else begin
  1484. AConnection.Disconnect;
  1485. end;
  1486. end;
  1487. if LEnd >= 0 then begin
  1488. VBuffer := Copy(VBuffer, LEnd + Length(MSG_END), MaxInt);
  1489. end;
  1490. until LEnd = -1;
  1491. if Length(VBuffer) > BUFFER_SIZE_LIMIT then begin
  1492. AConnection.Disconnect;
  1493. end;
  1494. except
  1495. // well, we need to suppress the exception, and force a reconnection
  1496. // we don't know why an exception has been allowed to propagate back
  1497. // to us, it shouldn't be allowed. so what we're going to do, is drop
  1498. // the connection so that we force all the network layers on both
  1499. // ends to reconnect.
  1500. // this is a waste of time if the error came from the application but
  1501. // this is not supposed to happen
  1502. try
  1503. AConnection.Disconnect;
  1504. except
  1505. // nothing - suppress
  1506. end;
  1507. end;
  1508. end;
  1509. function TIdHL7.HandleMessage(const AMsg: String; AConn: TIdTCPConnection; var VReply: String): Boolean;
  1510. var
  1511. LQueMsg: IIdQueuedMessage;
  1512. begin
  1513. Assert(Assigned(Self));
  1514. Assert(Length(AMsg) > 0, 'Attempt to handle an empty message'); {do not localize}
  1515. Assert(Assigned(FLock));
  1516. VReply := '';
  1517. Result := True;
  1518. try
  1519. case FCommunicationMode of
  1520. cmUnknown:
  1521. begin
  1522. raise EHL7CommunicationError.Create(Name, RSHL7ImpossibleMessage);
  1523. end;
  1524. cmAsynchronous:
  1525. begin
  1526. FOnMessageArrive(Self, AConn, AMsg);
  1527. end;
  1528. cmSynchronous, cmSingleThread:
  1529. begin
  1530. if IsListener then
  1531. begin
  1532. if FCommunicationMode = cmSynchronous then
  1533. begin
  1534. Result := False;
  1535. FOnReceiveMessage(Self, AConn, AMsg, Result, VReply);
  1536. end else
  1537. begin
  1538. LQueMsg := TIdQueuedMessage.Create(AMsg, FReceiveTimeout);
  1539. try
  1540. FLock.Enter;
  1541. try
  1542. FMsgQueue.Add(LQueMsg);
  1543. finally
  1544. FLock.Leave;
  1545. end;
  1546. LQueMsg.Wait;
  1547. // no locking. There is potential problems here. To be reviewed
  1548. VReply := LQueMsg.Reply;
  1549. finally
  1550. FLock.Enter;
  1551. try
  1552. FMsgQueue.Remove(LQueMsg);
  1553. finally
  1554. FLock.Leave;
  1555. end;
  1556. LQueMsg := nil;
  1557. end;
  1558. end;
  1559. end else
  1560. begin
  1561. FLock.Enter;
  1562. try
  1563. if FWaitingForAnswer then
  1564. begin
  1565. FWaitingForAnswer := False;
  1566. FMsgReply := AMsg;
  1567. FReplyResponse := srOK;
  1568. if FCommunicationMode = cmSynchronous then
  1569. begin
  1570. Assert(Assigned(FWaitEvent));
  1571. FWaitEvent.SetEvent;
  1572. end;
  1573. end else begin
  1574. // we could have got here by timing out, but this is quite unlikely,
  1575. // since the connection will be dropped in that case. We will report
  1576. // this as a spurious message
  1577. raise EHL7CommunicationError.Create(Name, RSHL7UnexpectedMessage);
  1578. end;
  1579. finally
  1580. FLock.Leave;
  1581. end;
  1582. end;
  1583. end;
  1584. else
  1585. begin
  1586. raise EHL7CommunicationError.Create(Name, RSHL7UnknownMode);
  1587. end;
  1588. end;
  1589. except
  1590. on e: Exception do
  1591. begin
  1592. if Assigned(FOnReceiveError) then begin
  1593. FOnReceiveError(Self, AConn, AMsg, e, VReply, Result);
  1594. end else begin
  1595. Result := False;
  1596. end;
  1597. end;
  1598. end;
  1599. end;
  1600. {==========================================================
  1601. Sending
  1602. ==========================================================}
  1603. // this procedure is not technically thread safe.
  1604. // if the connection is disappearing when we are attempting
  1605. // to write, we can get transient access violations. Several
  1606. // strategies are available to prevent this but they significantly
  1607. // increase the scope of the locks, which costs more than it gains
  1608. function TIdHL7.AsynchronousSend(const AMsg: String; ASyncConnection: TIdTCPConnection = nil): TSendResponse;
  1609. var
  1610. LBytes: TIdBytes;
  1611. begin
  1612. Result := srNone; // just to suppress the compiler warning
  1613. Assert(Assigned(Self));
  1614. Assert(Length(AMsg) > 0, 'Attempt to send an empty message'); {do not localize}
  1615. Assert(Assigned(FLock));
  1616. if GetStatus = isTimedOut then
  1617. begin
  1618. ReConnectFromTimeout;
  1619. end;
  1620. FLock.Enter;
  1621. try
  1622. if not Going then
  1623. begin
  1624. raise EHL7CommunicationError.Create(Name, IndyFormat(RSHL7NotWorking, [RSHL7SendMessage]));
  1625. end
  1626. else if GetStatus <> isConnected then
  1627. begin
  1628. Result := srNoConnection;
  1629. end
  1630. else if FIsServer then
  1631. begin
  1632. if (FCommunicationMode = cmAsynchronous) and Assigned(ASyncConnection) then
  1633. begin
  1634. LBytes := EncodeHL7Message(AMsg, FDefStringEncoding
  1635. {$IFDEF STRING_IS_ANSI}, FDefAnsiEncoding{$ENDIF}
  1636. );
  1637. ASyncConnection.IOHandler.Write(LBytes);
  1638. Result := srSent;
  1639. end
  1640. else if Assigned(FServerConn) then
  1641. begin
  1642. LBytes := EncodeHL7Message(AMsg, FDefStringEncoding
  1643. {$IFDEF STRING_IS_ANSI}, FDefAnsiEncoding{$ENDIF}
  1644. );
  1645. FServerConn.IOHandler.Write(LBytes);
  1646. Result := srSent;
  1647. end else begin
  1648. raise EHL7CommunicationError.Create(Name, RSHL7NoConnectionFound);
  1649. end;
  1650. end
  1651. else if Assigned(FClientThread) and Assigned(FClient) then
  1652. begin
  1653. LBytes := EncodeHL7Message(AMsg, FDefStringEncoding
  1654. {$IFDEF STRING_IS_ANSI}, FDefAnsiEncoding{$ENDIF}
  1655. );
  1656. FClient.IOHandler.Write(LBytes);
  1657. FClientThread.FLastTraffic := Ticks64; // TODO: sync this?
  1658. Result := srSent;
  1659. end else begin
  1660. raise EHL7CommunicationError.Create(Name, RSHL7NoConnectionFound);
  1661. end;
  1662. finally
  1663. FLock.Leave;
  1664. end;
  1665. end;
  1666. function TIdHL7.SynchronousSend(const AMsg: String; var VReply: String): TSendResponse;
  1667. begin
  1668. Assert(Assigned(self));
  1669. Assert(Length(AMsg) > 0, 'Attempt to send an empty message'); {do not localize}
  1670. Assert(Assigned(FLock));
  1671. Result := srError;
  1672. FLock.Enter;
  1673. try
  1674. FWaitingForAnswer := True;
  1675. FWaitStart := Ticks64;
  1676. FReplyResponse := srTimeout;
  1677. FMsgReply := '';
  1678. finally
  1679. FLock.Leave;
  1680. end;
  1681. try
  1682. Result := AsynchronousSend(AMsg);
  1683. if Result = srSent then
  1684. begin
  1685. Assert(Assigned(FWaitEvent));
  1686. FWaitEvent.WaitFor(FTimeOut);
  1687. end;
  1688. finally
  1689. FLock.Enter;
  1690. try
  1691. FWaitingForAnswer := False;
  1692. if Result = srSent then
  1693. begin
  1694. Result := FReplyResponse;
  1695. end;
  1696. if Result = srTimeout then
  1697. begin
  1698. if FIsServer then begin
  1699. DropServerConnection;
  1700. end else begin
  1701. DropClientConnection;
  1702. end;
  1703. end;
  1704. VReply := FMsgReply;
  1705. finally
  1706. FLock.Leave;
  1707. end;
  1708. end;
  1709. end;
  1710. procedure TIdHL7.SendMessage(const AMsg: String);
  1711. begin
  1712. Assert(Assigned(Self));
  1713. Assert(Length(AMsg) > 0, 'Attempt to send an empty message'); {do not localize}
  1714. Assert(Assigned(FLock));
  1715. if FWaitingForAnswer then begin
  1716. raise EHL7CommunicationError.Create(Name, RSHL7WaitForAnswer);
  1717. end;
  1718. FLock.Enter;
  1719. try
  1720. FWaitingForAnswer := True;
  1721. FWaitStart := Ticks64;
  1722. FMsgReply := '';
  1723. FReplyResponse := AsynchronousSend(AMsg);
  1724. finally
  1725. FLock.Leave;
  1726. end;
  1727. end;
  1728. function TIdHL7.GetReply(var VReply: String): TSendResponse;
  1729. begin
  1730. Assert(Assigned(Self));
  1731. Assert(Assigned(FLock));
  1732. FLock.Enter;
  1733. try
  1734. if FWaitingForAnswer then
  1735. begin
  1736. if GetElapsedTicks(FWaitStart) > FTimeOut then
  1737. begin
  1738. Result := srTimeout;
  1739. VReply := '';
  1740. FWaitingForAnswer := False;
  1741. FReplyResponse := srError;
  1742. end else begin
  1743. Result := srNone;
  1744. end;
  1745. end else
  1746. begin
  1747. Result := FReplyResponse;
  1748. if Result = srSent then
  1749. begin
  1750. Result := srTimeOut;
  1751. end;
  1752. VReply := FMsgReply;
  1753. FWaitingForAnswer := False;
  1754. FReplyResponse := srError;
  1755. end;
  1756. finally
  1757. FLock.Leave;
  1758. end;
  1759. end;
  1760. function TIdHL7.GetMessage(var VMsg: String): IInterface;
  1761. var
  1762. LQueMsg: IIdQueuedMessage;
  1763. begin
  1764. Result := nil;
  1765. Assert(Assigned(Self));
  1766. Assert(Assigned(FLock));
  1767. Assert(Assigned(FMsgQueue));
  1768. FLock.Enter;
  1769. try
  1770. if FMsgQueue.Count > 0 then
  1771. begin
  1772. LQueMsg := IIdQueuedMessage(FMsgQueue[0]);
  1773. VMsg := LQueMsg.Message;
  1774. FMsgQueue.Delete(0);
  1775. FHndMsgQueue.Add(LQueMsg);
  1776. Result := LQueMsg;
  1777. end;
  1778. finally
  1779. FLock.Leave;
  1780. end;
  1781. end;
  1782. procedure TIdHL7.SendReply(AMsgHnd: IInterface; const AReply: String);
  1783. var
  1784. LQueMsg: IIdQueuedMessage;
  1785. begin
  1786. Assert(Assigned(Self));
  1787. Assert(Assigned(AMsgHnd));
  1788. Assert(Length(AReply) > 0, 'Attempt to send an empty reply'); {do not localize}
  1789. Assert(Assigned(FLock));
  1790. FLock.Enter;
  1791. try
  1792. LQueMsg := AMsgHnd as IIdQueuedMessage;
  1793. LQueMsg.Reply := AReply;
  1794. LQueMsg.SetEvent;
  1795. FHndMsgQueue.Remove(LQueMsg);
  1796. finally
  1797. FLock.Leave;
  1798. end;
  1799. end;
  1800. end.