Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send multiple message over same TCP connection #32

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@
"key": "ctrl+alt+s",
"when": "editorLangId == hl7"
},
{
"command": "hl7tools.SendMultipleMessages",
"key": "ctrl+alt+g",
"when": "editorLangId == hl7"
},
{
"command": "hl7tools.StartListener",
"key": "ctrl+alt+l",
Expand Down Expand Up @@ -145,6 +150,10 @@
"command": "hl7tools.SendMessage",
"title": "HL7 Tools: Send Message"
},
{
"command": "hl7tools.SendMultipleMessages",
"title": "HL7 Tools: Send Multiple Messages"
},
{
"command": "hl7tools.StartListener",
"title": "HL7 Tools: Start Message Listener"
Expand Down
159 changes: 158 additions & 1 deletion src/SendHl7Message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ import * as net from 'net';
import * as tls from 'tls';
import { SendHl7MessagePanel } from './SendHl7MessageWebPanel';
import { ExtensionPreferences } from './ExtensionPreferences';
import { Delimiter} from './Util';


// MLLP framing codes
const VT = String.fromCharCode(0x0b);
const FS = String.fromCharCode(0x1c);
const FS = String.fromCharCode(0x1c);
const CR = String.fromCharCode(0x0d);

//----------------------------------------------------
Expand Down Expand Up @@ -105,6 +107,8 @@ export function SendMessage(Host: string, Port: number, HL7Message: string, Time
});
}



// handler for socket timeouts
client.on('timeout', () => {
webViewPanel.updateStatus('[' + new Date().toLocaleTimeString() + '] Connection to ' + Host + ':' + Port + ' has timed out waiting for a response. \r\n');
Expand Down Expand Up @@ -136,9 +140,162 @@ export function SendMessage(Host: string, Port: number, HL7Message: string, Time
Ack = Ack.replace(VT, "");
Ack = Ack.replace(FS + CR, "");
webViewPanel.updateStatus(Ack + '\r\n');
// client.destroy();
});

client.on('close', function (error: any) {
if (error) {
webViewPanel.updateStatus('[' + new Date().toLocaleTimeString() + '] Connection to ' + Host + ':' + Port + ' has been closed due to an error.\r\n');
}
else {
webViewPanel.updateStatus('[' + new Date().toLocaleTimeString() + '] Connection to ' + Host + ':' + Port + ' has been closed \r\n');
}
webViewPanel.updateStatus('\r\n');
});
}

//----------------------------------------------------
// Send a HL7 v2.x message to a remote host using MLLP framing.
// @param {string} Host - the DNS hostname or IP address of the remote host
// @param {int} Port - the TCP port of the remote host receiving the message.
// @param {string} HL7Message - a string representing a HL7 v2.x message
// @param {int} Timeout - the timeout value for the TCP socket in milliseconds. Defaults to 5000 if not supplied.
// @param {bool} UseTLS - if true connect using TLS
// @param {} encoding
// @param {object} webViewPanel - reference to webview panel object so that status update messages can be returned
export function SendMultipleMessages(Host: string, Port: number, HL7Message: string, Timeout: number, UseTls: boolean, IgnoreCertError: boolean, encoding: BufferEncoding, webViewPanel: SendHl7MessagePanel) {

// default to 5 second timeout for TCP socket if not supplied as a parameter
Timeout = Timeout || 5000;

// Establish a TCP socket connection to the remote host, write the HL7 message to the socket.
var preferences: ExtensionPreferences = new ExtensionPreferences();

var delimiters: Delimiter = new Delimiter();
delimiters.ParseDelimitersFromMessage(HL7Message);

var mshRegEx: RegExp = new RegExp("^MSH\\" + delimiters.Field, "gim");
var split: string[] = HL7Message.split(mshRegEx);

// loop through all matches, discarding anything before the first match (i.e batch header segments, or empty strings if MSH is the first segment)
for (var i = 1; i < split.length; i++) {
var newMessage = "MSH" + delimiters.Field + split[i];
split[i] = newMessage;

// replace any newlines added by the text area with CRs.
split[i] = split[i].replace(new RegExp('\n', 'g'), String.fromCharCode(0x0d));
}

var client: any;

//Keep track of message number
var j = 1;

// connect with TLS
if (UseTls) {

const tlsOptions = {
host: Host,
rejectUnauthorized: true // connections fail if this is set to false (??)
}

// load custom trusted CAs defined in user preferences
const trustedCAList: string[] = preferences.TrustedCertificateAuthorities;

// patch CreateSecureContext to add in custom CAs
// based on the Monkey Patch discussed in https://medium.com/trabe/monkey-patching-tls-in-node-js-to-support-self-signed-certificates-with-custom-root-cas-25c7396dfd2a
const origCreateSecureContext = tls.createSecureContext;
(tls.createSecureContext as any) = function(options: tls.SecureContextOptions) { // type assertion as any to work around read only property compiler warning
const context = origCreateSecureContext(options);
var pem: string = "";
for (let i:number = 0; i < trustedCAList.length; i++) {
if (fs.existsSync(trustedCAList[i])) {
pem += fs.readFileSync(trustedCAList[i], { encoding: "ascii" }).replace(/\r\n/g, "\n");
}
else {
console.log('User provided trusted CA not found: ' + trustedCAList[i]);
}
}
if (pem) {
const certs = pem.match(/-----BEGIN CERTIFICATE-----\n[\s\S]+?\n-----END CERTIFICATE-----/g);
if (!certs) {
console.log('Could not parse user defined root CA certificate(s)');
}
else {
certs.forEach(cert => {
context.context.addCACert(cert.trim());
});
}
}
return context;
};

client = tls.connect(Port, tlsOptions, function () {
// check for certificate validation errors
if (client.authorized) {
webViewPanel.updateStatus('[' + new Date().toLocaleTimeString() + '] Connected to ' + Host + ':' + Port + ' using TLS \r\n');
client.write((VT + HL7Message + FS + CR), encoding);
webViewPanel.updateStatus('[' + new Date().toLocaleTimeString() + '] Message number ' + j + ' sent \r\n');
}
else {
webViewPanel.updateStatus('[' + new Date().toLocaleTimeString() + '] TLS connection to ' + Host + ':' + Port + ' failed \r\n');
}
});

}
// connect without TLS
else {
client = new net.Socket();
client.setTimeout(Timeout);
client.setEncoding(encoding);
client.connect(Port, Host, async function () {
webViewPanel.updateStatus('[' + new Date().toLocaleTimeString() + '] Connected to ' + Host + ':' + Port + '\r\n');
client.write((VT + split[0] + FS + CR), encoding);
webViewPanel.updateStatus('[' + new Date().toLocaleTimeString() + '] Message number ' + j + ' sent \r\n');
});
}

// handler for socket timeouts
client.on('timeout', () => {
webViewPanel.updateStatus('[' + new Date().toLocaleTimeString() + '] Connection to ' + Host + ':' + Port + ' has timed out waiting for a response. \r\n');
client.destroy();
});

client.on('error', function (e: any) {
if (e.code == 'ECONNREFUSED') {
webViewPanel.updateStatus('[' + new Date().toLocaleTimeString() + '] Connection refused by ' + Host + ':' + Port + '\r\n');
}
else {
webViewPanel.updateStatus('[' + new Date().toLocaleTimeString() + '] An error occurred: ' + e.code + '\r\n');
if (e.code == 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
webViewPanel.updateStatus('[' + new Date().toLocaleTimeString() + '] Self Signed Certificates not supported.\r\n');
}
}
});

// error handler for sockets ended by remote endpoint.)
client.on('end', function (data: any) {
webViewPanel.updateStatus('[' + new Date().toLocaleTimeString() + '] Socket closed by remote host. \r\n');
});

// receive ACK, log to console
client.on('data', function (data: any) {
// convert the ACK response to string, remove the MLLP header and footer characters.
var Ack: string = data.toString(encoding);
webViewPanel.updateStatus('[' + new Date().toLocaleTimeString() + '] ACK Received: \r\n');
Ack = Ack.replace(VT, "");
Ack = Ack.replace(FS + CR, "");
webViewPanel.updateStatus(Ack + '\r\n');
j++
if (j>=split.length){
client.destroy();
}
else{
client.write((VT + split[j] + FS + CR), encoding);
webViewPanel.updateStatus('[' + new Date().toLocaleTimeString() + '] Message number ' + j + ' sent \r\n');
}
});

client.on('close', function (error: any) {
if (error) {
webViewPanel.updateStatus('[' + new Date().toLocaleTimeString() + '] Connection to ' + Host + ':' + Port + ' has been closed due to an error.\r\n');
Expand Down
57 changes: 56 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ExtractAllFields, ExtractReturnCode } from './ExtractFields';
import { HighlightFields, HighlightFieldReturnCode } from './HighlightField';
import { DisplaySegmentAsTree } from './FieldTreeView';
import { MaskAllIdentifiers } from './MaskIdentifiers';
import { SendMessage } from './SendHl7Message'
import { SendMessage, SendMultipleMessages } from './SendHl7Message'
import { StartListener, StopListener } from './TCPListener';
import { CheckAllFields } from './CheckRequiredFields';
import { MissingRequiredFieldResult } from './CheckRequiredFieldsResult';
Expand Down Expand Up @@ -440,6 +440,61 @@ export function activate(context: vscode.ExtensionContext) {

context.subscriptions.push(SendMessageCommand);

//-------------------------------------------------------------------------------------------
// This function sends the message in the active document to a remote host via TCP. The HL7 message is framed using MLLP.
var SendMultipleMessagesCommand = vscode.commands.registerCommand('hl7tools.SendMultipleMessages', function () {

console.log("Sending HL7 message to remote host");

// get the user defaults for TCP Connection timeout & FavouriteRemoteHosts
const tcpConnectionTimeout = preferences.ConnectionTimeOut * 1000;

var activeEditor = vscode.window.activeTextEditor;
if (!activeEditor) {
console.log("No document open, nothing to send. Exiting 'hl7tools.SendMessage'");
return;
}

// get the HL7 message from the active document. Convert EOL to <CR> only.
var currentDoc = activeEditor.document;
var hl7Message = currentDoc.getText();
// get the EOL character from the current document
var endOfLineChar: string = Util.GetEOLCharacter(currentDoc);
hl7Message = hl7Message.replace(new RegExp(endOfLineChar, 'g'), String.fromCharCode(0x0d));

// display the webview panel
var thisExtension: vscode.Extension<any> | undefined = vscode.extensions.getExtension('RobHolme.hl7tools');
if (thisExtension === undefined) {
console.log("The extension 'RobHolme.hl7tools' could no be referenced.")
return;
}
var SendHl7MessageWebView: SendHl7MessagePanel = new SendHl7MessagePanel(thisExtension.extensionUri);
if (preferences.SocketEncodingPreference) {
SendHl7MessageWebView.encodingPreference = preferences.SocketEncodingPreference;
}
SendHl7MessageWebView.render(hl7Message);
// add any favourites from the user preferences to the webpanel's dropdown list
SendHl7MessageWebView.updateFavourites(preferences.FavouriteRemoteHosts);

// handle messages from the webview
SendHl7MessageWebView.panel.webview.onDidReceiveMessage(function (message) {
switch (message.command) {
case 'sendMessage':
SendMultipleMessages(message.host, message.port, message.hl7, tcpConnectionTimeout, message.tls, message.ignoreCertError, message.encoding, SendHl7MessageWebView);
return;
case 'exit':
SendHl7MessageWebView.panel.dispose();
return;
}
},
undefined,
context.subscriptions
);
});

context.subscriptions.push(SendMultipleMessagesCommand);


//-------------------------------------------------------------------------------------------
// This function receives messages from a remote host via TCP. Messages displayed in the editor as new documents.
var StartListenerCommand = vscode.commands.registerCommand('hl7tools.StartListener', function () {
Expand Down