PTY, Telnet, and MUMPS_COMMAND
Contents
When Cadence GUI entered the Epic stage, the team was provided with a complete copy of Legacy/EpicCare to do with as we saw fit. There was zero process in place for any formal code sharing at the time, so we stripped our copy down to the bare bones leaving only a small communication engine and a developer hub Visual Basic Form (that I plan on talking about later).
Options for input and output with a socket from the MUMPS server were very limited back when EpicCare was started. While there were built-in functions for communicating to external devices, they were focused on brief communications and had very limited control options. Acting as a “server” for a long running persistent communication channel was far more challenging. Further, the support across vendors for options wasn’t consistent.
Faced with this communication challenge, the Legacy team built a resourceful alternative. All MUMPS server hosts supported connections via a (now thankfully waning) protocol called Telnet. The Telnet protocol shows its age these days and isn’t commonly used (and was phased out at Epic decades ago). But in 1992, it was a common service that was available on all operating system platforms Epic and customers were using.
As a brief aside, as the Telnet protocol was an operating system service, each OS (and version!) had its fair share of quirks. It was fortunately uncommon, but we would encounter situations where a version of an OS, for example HP/UX, would improperly handle a documented Telnet command. Of course, as bugs often go, it was only in certain circumstances and combinations which made troubleshooting cumbersome.
MUMPS_Command
was Born
The Legacy team exposed the functionality in Visual Basic by creating a function called MUMPS_Command
. When the Visual Basic application launched, using a configuration file that was dubiously secured, tied with an application hard-coded decoding key, the host application would make a Telnet connection to the MUMPS server host OS.
It would then send … locally stored credentials … (sigh, yes, that’s how it worked back then). Upon a successful login and some scripting magic in the OS, a MUMPS job (process) was started immediately and began to execute a specific MUMPS routine. This was a “captive” session.
If someone knew the user name and password for this special MUMPS_Command
user, they’d be launched directly into the Epic created protocol for communication from client to server.
Some of you may have used a Telnet client to connect to a OS service for troubleshooting.
> TELNET localhost 5555
GET / HTTP/1.0
It definitely doesn’t put the “fun” back in functional though.
After a brief exchange of a control sequence to verify the connection and some important settings, MUMPS_Command
was ready for duty. Although “doody”(💩) may be more appropriate for the early versions.
How did it work?
I cannot say that the early versions of MUMPS_Command
were robust, or secure, or reliable. They weren’t. Telnet isn’t secure. The protocol and services are terrible and have no baked-in security. And yet at the time, Telnet was used most often for connecting to a remote host. Using Telnet back in the mid 1990s wasn’t unusual, so Epic using the protocol as a connection didn’t raise any general concern across customers. (Live encryption of this data on the wire would have been unheard of back then given it was running on a secured Intranet and the massive increase in computing power required made it a non-starter).
The core idea was that a captive MUMPS job/process was either running MUMPS code or waiting for input from the end user via a terminal or pseudo-terminal (teletype TTY
or pseudo-teletype PTY
). Given that the Visual Basic client (Cadence and EpicCare) were remote connections to this captive process, the MUMPS code was in an infinite loop.
COMM NEW input,done,TIMEOUT
SET TIMEOUT=300
WAIT ;
READ input:TIMEOUT
GOTO WAIT
It’s a slight oversimplification of what the code looked like, but it’s not far off. It’s not done yet. (I also used non-abbreviated MUMPS instruction names).
The Visual Basic client, over the Telnet protocol would “send” a request to the captive MUMPS session by “typing” it and sending a carriage return (0x0D
). The connected MUMPS job would receive the text and store it in the variable input
as shown above. Using the syntax as shown above, the MUMPS READ
command only ends when it receives a line of input (ended by a ‘terminator’ character which included the carriage return).
Now that the input
has the request sent from the Visual Basic application, what’s next? The team decided on what became an unfortunate choice a number of years later. It was effective and easy. But it was fragile. And it offered no reasonable security (especially in the early versions).
MUMPS has a wonderful command/instruction called Xecute
(think of it as eXecute). The string provided to this command can be any valid MUMPS expression is immediately executed. Many interpreted languages have a similar feature. JavaScript has eval
for example. eval
has been used many times over the years for perfectly fine JavaScript browser code, humorous hacks, and too many noteworthy nefarious reasons.
Remember, this Xecute
command allows execution of any valid expression.
Some of you may be shuddering already. Good!
COMM NEW input,done,TIMEOUT
SET TIMEOUT=300
WAIT ;
READ input:TIMEOUT
IF input="**END**" GOTO EXIT
XECUTE input
GOTO WAIT
EXIT ;
WRITE !,"**ENDED**"
(It’s weird for me to type out the full command XECUTE
, as I don’t know that in my 25 years of Epic I ever used anything but the abbreviated X
!)
The loop reads the input, checks to see if the client connection wants to end the connection, and if not, executes the expression that was passed.
As requests need a response, the expression sent needed to store the result in the variable X
.
S X=$$getFut^SCHED1(" 20240705",53711)
The Xecute
command parsed the expression and executed it. In the case above, the code is calling into a second MUMPS routine to get future appointments for a specific patient identifier from a specific date.
Upon evaluating the expression, the MUMPS_Command
WAIT
loop would write to the Telnet session whatever value was stored in the variable X
.
COMM NEW input,done,TIMEOUT
SET TIMEOUT=300
WAIT ;
READ input:TIMEOUT
IF input="**END**" GOTO EXIT
XECUTE input
W X ; sent back to client
GOTO WAIT
EXIT ;
WRITE !,"**ENDED**"
Annoyances
MUMPS for much of its existence has had extremely short string storage/length limits in memory and when stored in MUMPS Globals. Because of this limitation, and because of how the Epic invented protocol worked at the time, it meant that the Visual Basic developer had to make certain that the MUMPS_Command
requests did not exceed the string limit (this number was passed to the client as part of the initial handshake).
Long requests and responses had to be broken into segments. It was very common to need to send a “request #
of N
” as part of a series of MUMPS_Command
calls. A free text field on the client that was longer than allowed by MUMPS required that the developer break the string into pieces (pun intended) and send them in chunks. Reverse that for the server sending that same field to the client. Learning to return a “is there more work to do” was a common pattern on client and server.
Line terminator characters had be carefully transformed before being sent over Telnet to the server. If an errant 0x0D
(carriage return) was in the data, it would cause the XECUTE
to begin evaluation of the expression immediately. Then … the whole communication channel was broken as the client and the server protocol would be mismatched — the protocol was a simple state machine on both ends. Either waiting or receiving. There was no way for an out of band communication to be handled properly.
Line feeds and carriage returns were transformed (or $TRanslated
in MUMPS) to non-line-terminating characters 0x01 and 0x02. Occasionally, those would be the source of weird Telnet service issues over the years. 😒
Little Bobby Tables
If you’ve done any database development in the last decade, you’ve likely seen this:
Well, MUMPS_Command
had a problem that was an extension of the XECUTE
command. The XECUTE
command evaluated and executed all valid code.
If the client sent an unsanitized string, like a patient name with a value of: JORDAN, MICHAEL ") K ^ER S %1=(""
This would get sent to the server:
S X=setName^DEMOG("JORDAN, MICHAEL") K ^GLOBAL S %1=("")
Rather than the intended:
S X=setName^DEMOG("JORDAN, MICHAEL")
Needless to say, there was some non-Midwest nice words used in the offices for a few days as developers scrambled to fix the issue.
It really wasn’t ideal that a user of a client application could say, delete an entire global structure: K ^GLOBAL
. That’s syntax in MUMPS for deleting an entire GLOBAL (KILL ^GLOBAL
). This would be similar to a SQL statement where an entire table is dropped (and a huge disaster).
I’m not aware of any customer related security issue to this design. The lack of properly handling quotes caused application stability issues most noticeably.
There became a little “quote” coding challenge to verify that there wasn’t unexpected quotes and that all Xecute
s worked with expressions that had been sanitized properly.
Licensing
Epic was more frugal in the 1990s than it has been in decades. When the company needed to spend money, it did, but reluctantly, and not without putting up a licensing fee fight. For many years, Epic relied on a third party vendor for providing a Visual Basic compatible Telnet client (weird weird since Epic has so often been considered a “We Build It Here” software factory).
While I believe there were two vendors that had been used, the most used vendor was from a company called Distinct. The product was called … Distinct Telnet. (HOLY MIDWEST COWS READER! Distinct exists and still sells an ActiveX version via what seems to be their FrontPage 98 web front end using ASP web pages). Frankly, it made little financial sense for Epic to write that code. Visual Basic wasn’t fast enough yet to make it viable (as it wasn’t compiled to native machine code in early versions), and there was no one with experience in creating a Visual Basic 2/3 component using C/C++ for a communication protocol, and with Telnet.
Eventually Epic reached a licensing deal with the authors of the Telnet component that seemed high at the time. The real nuisance was that it was a per-install license for customers and Epic internal use rather than a site-license. Epic software wasn’t licensed “per-machine” so tracking client devices was unnatural.
Hello Epic Telnet!
Years later, when I was the team lead of Foundations, we embarked on creating our own Telnet implementation as the costs of the Distinct Telnet component had risen and it was difficult to justify the cost to all customers — and more importantly it wasn’t as fast as we thought it should be. During the course of a few months, a talented developer created “Epic Telnet” and we tested it internally (albeit slowly and with a significant amount of internal trepidation); after a significant amount of internal testing we began to cautiously install and use it at new customers. While my team and my own feelings were more optimistic and pragmatic, I know that others felt this new Epic Telnet was far too risky to implement and far too outside of the norm for us to be developing. While I understood their apprehensive and cautious response, the deployments went well and we demonstrated how the new communication system was more reliable and faster than it had been using just Distinct Telnet (in part, not due to a fault in Distinct Telnet, but more being able to tune our code to our exact needs). The addition of Epic Telnet removed a cost and several pain-points related to the older component. Strangely, I know it took YEARS for the older Telnet functionality to be fully removed and not used. It was a slow process, not entirely atypical for healthcare IT I suppose across the spectrum of customers and cultures.
A Positive Connection
A huge amount of work was performed over the years without issue with this simple protocol created at Epic. Sometimes simple to start is all that is needed, and for Epic, it was a great early fit. It had growing pains over the years though.
We did some other cool things over the years with this Telnet based communication system, but this post has gone long …, so for another time!
MUMPS_Command
becomes M_Command
becomes RPC_Command
In the mid-1990s, using the name “MUMPS” was becoming less tolerated by potential customers and the ISO standard had approved using M
as an alternative. For reasons that are still amusing to me, Carl “strongly requested” that all uses of MUMPS_Command
be renamed to M_Command
. While customers weren’t encountering the name (unless they were browsing the source code, or doing their own custom development), dropping the name meant it was less commonly spoken and less used during every-day conversation at Epic. M was the new better name, for reasons. It later became RPC_Command
fully dropping the M
in favor of Remote Procedure Calls as that sounded more inline with terminology and techniques that were gaining favor at the time (mid-late 1990s).
I’m going to stick with MUMPS for a while longer though, unless Carl pays me to stop. 🤓
Hi! Before you go...🙏
I really appreciate you stopping by and reading my blog!
You might not know that each Epic blog post takes me several hours to write and edit.
If you could help me by using my Amazon affiliate links, it would further encourage me to write these stories for you (and help justify the time spent). As always, the links don't add cost to the purchase you're making, I'll just get a little something from Amazon as a thanks.
I'll occasionally write a blog post with a recommendation and I've also added a page dedicated to some of my more well-liked things. While you can buy something I've recommended, you can also just jump to Amazon and make a purchase. Thanks again!