THE LLP-DRIVER The LLP support that is currently provided with ProtoGen/HL7 is somehow a hack, driven by the urgent need for an implementation of ANSI X3.28 protocol, I decided to write an LLP translator that transforms ANSI X3.28 sessions to a simpler LLP. For the latter, I chose what I called "Internal LLP" (ILLP) later, since it was designed for the communication among HL7 applications built upon ProtoGen/HL7. The ILLP is a very simple LLP designed for the use with C++ IOstream classes. Basically it signals the end of a message by an end-of-file token. Thus, reading a HL7 message from a communication channel becomes just the same as reading it from a file (one message per file). No need to track end-of-message delimiter characters as when using the MLLP or HLLP or more. This can easily be done with BSD sockets (i.e. TCP/IP or UNIX-Stream sockets): The a stream socket, which is a full-duplex communication channel, can, once it is connected, be shut down on just one half. I.e. you can close just your writeable half of the channel and still receive data on the readable half. The peer will recognize that you have closed your writable half by receiving and end-of-file token (i.e. read() returns 0). Now the peer can send it's reply data through the still open half that you are reading on. Thus, the ILLP favors a specific view on a (HL7) communication session which is outlined in Fig. 1: A request message is sent from the client to the server after which the client closes it's write-half of the connection signalling end of message to the server. Now the server interprets the request message, performs the service and sends the reply message (typically ACK) via it's still usable write-half of the connection (which is the read-half for the client). This is now closed as well, which signals end of message to the client. By now, the connection became unusable, because either party has closed it's write-half. Since this is so, the socket is deleted and the session is finished. C S L -------- REQUEST -----------> E ----+ I R PERFORM E V SERVICE N <------- REPLY -------------- E <---+ T R Fig. 1: An ILLP session. Since the other LLPs (MLLP, HLLP, ANSI X3.28) define exceptional conditions like restart of a message, aborts and interrupts, I needed a simple way to handle these exception without using C++ exceptions and without always having to test for all these special conditions. C++ exceptions would have been the preferred means, but the didn't exist in the GNU C++ compiler I used these days. So I decided to use the support for exceptions that comes with any Unix system, namely signals. A TCP/IP connection can receive exceptions (called urgent or out-of-band data) and signal it to the controlling process. Thus, restart and abort/cancel exceptions of ANSI X3.28 and MLLP/HLLP translate to a byte of out-of-band data, telling you what kind of exception was raised: ASCII.STX means restart and ASCII.CAN means cancel the message. Even though the longjump from signal handlers is not a proper solution, since resources are not deallocated, it is a usable method, provided that the process exits at a time `not too long' after the exception signal was raised and provided that no resources are allocated that would cause the same process to block if allocation of these resources would be redone after the exception. Fortunately a TCP/IP server normally forks serving processes which exit when a single request was served. Thus, taken it's limitations into account, this method seemed usable. Now, how does the LLP driver work? Rather than being linked into the program that needs to use the LLP, the LLP driver is a separate program, called `llpdrv'. This process is started once for each communication channel that should be driven by one of the supported LLP. For example: consider you have a serial line /dev/cua00, which is connected to a HL7 application `CaVe' that requires ANSI X3.28 LLP. Your application, called `MyApp' can communicate via ILLP/TCP. You would start the LLP driver once as follows: llpdrv -a -t /dev/cua00 -i mahl7%myhost -i cvhl7 This starts an LLP-driver process that listens on the channel /dev/cua00 (referred to as "the line") for any ANSI X3.28 communication to start (e.g. ENQ). When this happens, it connects your application on the host `myhost' and TCP service/port `mahl7' via ILLP/TCP. MyApp will receive the message that was sent by CaVe but packet into ILLP instead of ANSI X3.28 LLP. MyApp will reply to the message just received in the same thread. Now what's `cvhl7'? So far we have only shown how the LLP driver is used if the CaVe System is the client and the MyApp is the server. If the two need to change roles, the client (MyApp) can connect the server (CaVe) via the TCP service/port `cvhl7' on the host where the llpdrv process is running. An ILLP session is now directed to the CaVe System via ANSI X3.28. As you might have noted, an llpdrv process is two servers in one: The server that serves the port `cvhl7' is called the distal server, while the server that serves "the line" is called the proximal server. Each server is run by an extra process (forked off the initial llpdrv process). The two concurrent processes that use the line, are managed by a mutual exclusion method, which is either a semaphore (if your OS supports SYSV semaphores) or the trip/trap signalling method. There are certain other options to the llpdrv program that are shown in the following synopsis: llpdrv [-dyY] [-a|-h|-m] [[-i|-u|-p|-t] from] [-i to] [-i dist] Options are: LLP to use: -a ANSI X3.28 -h Hybrid LLP (HLLP) -m Minimal LLP (MLLP) The line -i TCP/IP -u a UNIX stream socket -p a pseudo terminal (PTY) -t a terminal/serial line (TTY) The outgoing connections (`to') and distal server (`dist') are always ILLP driven and use TCP/IP, therefore the option is always -i TCP/IP Miscellaneous options -I Don't wait for the proximal client to do something but directly establish the distal service. Otherwhise the LLP driver listens on the line for an outgoing message (like NMD) -1 One shot mode. Disconnect from the line after each transaction. -y and -Y Don't fork the llpdrv off the controlling terminal. This is useful for debugging purpose or when started by init via /etc/ttys or /etc/inittab. Normally the llpdrv forks directly into the background, disassociates from the controlling terminal and establishes a new process group to which any of it's sub-processes belong. The number of different processes and roles that the llpdrv program establishes might be a bit confusing. Please read the leading comment of the file ProtoGen-*/llp/llp/llpdrv.c which discusses the different roles/processes extensively. The ILLP is documented in ProtoGen-*/doc/Internal-llp.