Go to the first, previous, next, last section, table of contents.

The LLP Driver Program


llpdrv [-dyY] [-a|-h|-m] [[-i|-u|-p|-t] from] [-i to] [-i dist]



The LLP support that is currently provided with ProtoGen 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) (see section The Internal Protocol) 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 writable 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 the figure below. A request message is sent from the client to the server after which the client closes it's write-half of the connection signaling 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

The LLP Driver

Rather than being linked into the program that needs to use the LLP, the LLP driver is a separate program, called llpdrv. It translates the session and transport protocol mechanisms of the ANSI X3.28, the "hybrid lower layer protocol" (HLLP), or the "minimal lower layer protocol" (MLLP), which are described in HL7 v2.1, Appendix B or the HL7 Implementation Guide, to the ILLP. Programs that need to communicate in either of these LLPs do only need to implement the simple ILLP and communicate to sites that require the other LLPs via an llpdrv process.

The llpdrv 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 packed into ILLP instead of ANSI X3.28 LLP. MyApp will reply to the message just received in the same thread.

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.

An llpdrv process is thus 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. The proximal client is the first process to contact the LLP driver. The destination server is the second process that the LLP driver gets contact with and, third, multiple distal clients can connect subsequently to the distal server.

  (1)       .                               .      (2)
 CLIENT     .    SERVER        |            .    SERVER
            .                  |            .
            .                  +---DISTAL---<<---DISTAL
            .                       SERVER  .    CLIENT
            .                               .     (3-n)
            ...........LLP DRIVER............

Roles and Processes

It is important to identify four different "roles" that the LLP driver processes can assume: Startup process, proximal server process, proximal serving process and distal server process.

First of all, there is the "startup process", which is the one that is usually called by execve(2) from a shell. The setup process exits as soon as it has:

There is nothing else printed to the standard output, thus the server process' id can be stored easily for an eventual shutdown of the LLP driver at a later point in time.

The "server process" is the process that accepts connections by a client. However, it does not serve the client itself but invokes a "serving process" that does the real work, before it loops back to accept more connections. This is because the Berkeley sockets are capable of handling multiple client-server connections that rendez-vous at the same socket in parallel (actually, rendez-vous is sequential but interaction is parallel). Since the tty(or pty)-socket abstraction is not a real Berkeley socket, multiple interactions are not possible with tty-sockets. Anyway, the server process stays resident throughout the lifetime of a running LLP driver. Thus the proximal server process is the main process which has to be sent a termination signal in order to shut down the whole LLP driver.

        |       |         
       exit   disassociate
       ====   from terminal
        .       |
      | +-------do
      | |       |
      | |     accept
      | |       |
      | |   connection
      | |       |       If socket is a tty don't fork but act like a child.
      | |     fork--------------+           From here on we must decide if  
      | |       |               |           we exit or continue on error    
      | |     close         allocate        conditions depending on if we   
      | |   connection       semaphore...   did fork or not.
      | |       |               |       :
      | +-------+             fork--------------+     A distal server is
      | .       .               |       :       |     always established
      | .       .           proximal    :    distal   except if we only
      | .       .            server...mutex...server  want a single trans-
      | .       .               |               |     action.
      | .       .          kill distal          |
  reopen tty    .            server ......... exit
      | .       .               |             ====
      +-------------------<if we didn't fork    .
        .       .              else             .
        .       .               |               .
        .       .              exit             .
        .       .              ====             .
        .       .               .               .
        .       .               .               .
    startup > proximal  >    proximal   >    distal
    process    server        serving         server (and serving)
              process        process         process
  transient   resident       transient       transient

The "serving process" is the process that actually serves the proximal client. The proximal connection is regarded as a full duplex serial line. This line is connected to another server, which is the destination of any transaction that is initiated from the proximal client. The destination server is connected by an INET socket and communication to it is driven by the so called "internal protocol" (see section The Internal Protocol). The protocol used at the proximal connection can be either a kind of ANSI X3.28, the HLLP, or the MLLP.

The capability of the proximal client to act as a server is propagated by the LLP driver by means of the "distal server" which is a child process of the proximal serving process. The distal server binds an INET socket which can however serve only one transaction at a time (non-multiplexed). The distal connection is driven by the "internal protocol" since the "line" is regarded as a non-multiplexed line. Because the proximal and distal clients use the line concurrently, there is a need for a mutual exclusion mechanism that guarantees that either the proximal serving process or the distal serving process may use the line. The process that has permissions is said to "have the line", while the other process is blocked in a wait state.


Only processes on the localhost may connect to the distal server of the LLP driver.


LLP to use at the proximal connection (the line):

ANSI X3.28
Hybrid LLP (HLLP)
Minimal LLP (MLLP)

The medium of the line:

TCP/IP address specified as `service%host'
a UNIX stream socket
a pseudo terminal (PTY)
a terminal/serial line (TTY)

The destination address (to), always ILLP driven using TCP:

-i service%host
TCP/IP service and hostname to connect in order to forward proximal requests

The distal server (dist), always ILLP driven using TCP:

-i service
TCP/IP servicename where the distal server can be connected

Miscellaneous options

If a TTY (`-t') or PTY (`-p') is used as the line, don't wait for the proximal client to do something but immediately establish the distal service. Otherwise the LLP driver listens on the line for an outgoing message (like NMD) before the distal service is established.
One shot mode. Disconnect from the line after each transaction. No distal server is established.
Don't fork the proximal server process off the startup process.
Don't fork the proximal server process off the startup process and don't disassociate from the controlling terminal. The `-Y' and `-y' options are useful for debugging purpose or when started by init(8) 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.



ttyd0  llpdrv -YIa -t /dev/ttyd0 -i discard%localhost -i carevuefsi


The ANSI X3.28 protocol implementation has difficulties with very hasty peers, i.e. those whose timeout timer values are small. 1 s is safe.

The ANSI X3.28 protocol options are only adjustable at compile time.

The HLLP protocol is not actually implemented but only it's empty "slot" is provided.

The MLLP protocol was lat tested long ago, it might be broken in the meantime.

The LLP driver is more complex than necessary.

The LLP driver is less complex than necessary.

The LLP driver introduces too much overhead involving the ILLP communication.

The LLP driver will be replaced by a library approach which is more comprehensive and flexible. Thus the LLP driver itself is not supported on the long run.

See Also

hosts(5), services(5), section The Internal Protocol.

Go to the first, previous, next, last section, table of contents.