Skip to content

Comments

Add MLLP module with optional TLS/SSL support#22

Open
Copilot wants to merge 6 commits intomasterfrom
copilot/implement-mllp-module-server-client
Open

Add MLLP module with optional TLS/SSL support#22
Copilot wants to merge 6 commits intomasterfrom
copilot/implement-mllp-module-server-client

Conversation

Copy link
Contributor

Copilot AI commented Feb 20, 2026

Implements the MLLP (Minimal Lower Layer Protocol) transport layer for nodehl7, enabling real-time HL7 message exchange over TCP/IP with optional TLS encryption.

MLLP framing (src/mllp.ts)

  • wrap(data) / unwrap(buffer) — MLLP framing using VT (0x0B) start and FS+CR (0x1C 0x0D) end markers, with correct handling of fragmented streams and multiple messages per packet

MLLPServer

  • Composition-based design wrapping net.Server or tls.Server depending on options
  • Per-socket buffering for fragmented TCP delivery
  • Emits hl7_message events; also accepts a handler callback in constructor
  • TLS mode via { tls: { key, cert } } options

MLLPClient

  • Promise-based send() returning the server's response
  • TLS mode via { tls: { rejectUnauthorized, ca } } options

Usage

const { MLLPServer, MLLPClient } = require('nodehl7');

// Plain TCP
const server = new MLLPServer((message, reply) => {
  reply('MSH|^~\\&|...|ACK|...\rMSA|AA|123');
});
server.listen(2575);

// TLS
const tlsServer = new MLLPServer(handler, {
  tls: { key: fs.readFileSync('key.pem'), cert: fs.readFileSync('cert.pem') }
});

const client = new MLLPClient('127.0.0.1', 2575, {
  tls: { rejectUnauthorized: false }
});
const ack = await client.send(hl7Message);
client.close();

Other

  • Re-exported from main nodehl7 entry point (CJS + ESM)
  • Version bump to 0.3.0
  • 14 tests covering framing, fragmentation, concurrency, event API, and TLS

🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

Copilot AI and others added 2 commits February 20, 2026 12:39
Co-authored-by: Loksly <1235649+Loksly@users.noreply.github.com>
Co-authored-by: Loksly <1235649+Loksly@users.noreply.github.com>
Copilot AI changed the title [WIP] Implement MLLP module for Node.js net standards Implement MLLP transport module for HL7 message exchange over TCP Feb 20, 2026
Copilot AI requested a review from Loksly February 20, 2026 12:42
Copilot AI and others added 2 commits February 20, 2026 12:47
Co-authored-by: Loksly <1235649+Loksly@users.noreply.github.com>
Co-authored-by: Loksly <1235649+Loksly@users.noreply.github.com>
Copilot AI changed the title Implement MLLP transport module for HL7 message exchange over TCP Add MLLP module with optional TLS/SSL support Feb 20, 2026
@Loksly Loksly marked this pull request as ready for review February 20, 2026 12:52
Copilot AI review requested due to automatic review settings February 20, 2026 12:52
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds MLLP (Minimal Lower Layer Protocol) transport layer support to nodehl7, enabling real-time HL7 message exchange over TCP/IP with optional TLS encryption. The implementation includes framing functions for wrapping/unwrapping messages with MLLP delimiters, an event-driven MLLPServer for handling incoming connections, and a promise-based MLLPClient for sending messages.

Changes:

  • Added MLLP framing implementation with wrap() and unwrap() functions supporting VT start byte and FS+CR end markers
  • Implemented MLLPServer and MLLPClient classes with optional TLS/SSL support
  • Added comprehensive test suite with 14 tests covering framing, fragmentation, concurrency, event-based API, and TLS scenarios

Reviewed changes

Copilot reviewed 4 out of 20 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/mllp.ts New MLLP module implementing framing functions, MLLPServer, and MLLPClient with TLS support
src/hl7.ts Updated to re-export MLLP classes and functions for both CommonJS and ES module consumers
test/testMllp.js Comprehensive test suite covering MLLP framing, server/client functionality, and TLS encryption
package.json Version bump to 1.1.0 (note: discrepancy with PR description stating 0.3.0)
dist/* Compiled JavaScript and TypeScript definition files for both CommonJS and ES modules

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

{
"name": "nodehl7",
"version": "0.2.6",
"version": "1.1.0",
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description states "Version bump to 0.3.0" but the actual package.json shows version 1.1.0. This is a significant discrepancy that needs to be resolved. Either the description needs to be updated or the version in package.json should match the description.

Copilot uses AI. Check for mistakes.
Comment on lines +246 to +263

const onData = (chunk: Buffer): void => {
buffer = Buffer.concat([buffer, chunk]);
const { messages } = unwrap(buffer);
if (messages.length > 0) {
socket.removeListener('data', onData);
socket.removeListener('error', onError);
resolve(messages[0]);
}
};

const onError = (err: Error): void => {
socket.removeListener('data', onData);
reject(err);
};

socket.on('data', onData);
socket.on('error', onError);
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MLLPClient's send() method doesn't clean up event listeners if the buffer never receives a complete message. If the server sends partial data but never completes the message, the onData and onError listeners will remain attached to the socket indefinitely, causing a memory leak. Consider adding a timeout mechanism or ensuring cleanup happens when the connection closes.

Suggested change
const onData = (chunk: Buffer): void => {
buffer = Buffer.concat([buffer, chunk]);
const { messages } = unwrap(buffer);
if (messages.length > 0) {
socket.removeListener('data', onData);
socket.removeListener('error', onError);
resolve(messages[0]);
}
};
const onError = (err: Error): void => {
socket.removeListener('data', onData);
reject(err);
};
socket.on('data', onData);
socket.on('error', onError);
let settled = false;
let timeout: NodeJS.Timeout | null = null;
const cleanup = (): void => {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
socket.removeListener('data', onData);
socket.removeListener('error', onError);
socket.removeListener('close', onClose);
};
const settleResolve = (message: Buffer): void => {
if (settled) {
return;
}
settled = true;
cleanup();
resolve(message);
};
const settleReject = (err: Error): void => {
if (settled) {
return;
}
settled = true;
cleanup();
reject(err);
};
const onData = (chunk: Buffer): void => {
buffer = Buffer.concat([buffer, chunk]);
const { messages } = unwrap(buffer);
if (messages.length > 0) {
settleResolve(messages[0]);
}
};
const onError = (err: Error): void => {
settleReject(err);
};
const onClose = (): void => {
if (!settled) {
settleReject(new Error('Connection closed before complete MLLP response was received'));
}
};
// Set a timeout to avoid hanging indefinitely if a complete message is never received.
timeout = setTimeout(() => {
if (!settled) {
settleReject(new Error('Timed out waiting for MLLP response'));
}
}, 30000);
socket.on('data', onData);
socket.on('error', onError);
socket.on('close', onClose);

Copilot uses AI. Check for mistakes.
}

socket.on('error', (err: Error) => {
this._connected = false;
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MLLPClient reuses sockets via the _connect() method's check on line 202. However, after a socket encounters an error (line 222-226), the _connected flag is set to false but the _socket reference might not be null if the error occurs before the 'close' event fires. This could lead to attempting to reuse a socket in an error state. Consider also setting _socket to null in the error handler.

Suggested change
this._connected = false;
this._connected = false;
this._socket = null;

Copilot uses AI. Check for mistakes.
Comment on lines +242 to +260
before(function () {
// Generate self-signed certificate for testing
var keys = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});

var execSync = require('child_process').execSync;
var fs = require('fs');
var tmpDir = require('os').tmpdir();
var keyPath = path.join(tmpDir, 'mllp-test-key.pem');
var certPath = path.join(tmpDir, 'mllp-test-cert.pem');
fs.writeFileSync(keyPath, keys.privateKey);
execSync('openssl req -new -x509 -key ' + keyPath + ' -out ' + certPath + ' -days 1 -subj "/CN=localhost" 2>/dev/null');
tlsCerts = {
key: keys.privateKey,
cert: fs.readFileSync(certPath, 'utf8')
};
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TLS certificate generation test setup relies on the openssl command-line tool being available in the PATH (line 256). This makes the tests platform-dependent and they will fail on systems without OpenSSL installed or in CI environments that don't have it. Consider using a pure Node.js solution like the node-forge library or the built-in crypto.X509Certificate API to generate self-signed certificates, or include pre-generated test certificates in the repository.

Copilot uses AI. Check for mistakes.
Comment on lines +497 to +502
const mllp = require('./mllp');
module.exports = hl7Parser;
module.exports.MLLPServer = mllp.MLLPServer;
module.exports.MLLPClient = mllp.MLLPClient;
module.exports.mllpWrap = mllp.wrap;
module.exports.mllpUnwrap = mllp.unwrap;
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CommonJS export approach uses require('./mllp') in an ES module context (line 497). While this may work in some build configurations, it's mixing module systems and could cause issues in strict ESM environments or with certain bundlers. Consider using a dynamic import or ensuring the build process handles this correctly. A cleaner approach would be to import the MLLP exports at the top of the file and re-export them consistently.

Copilot uses AI. Check for mistakes.

socket.on('error', (err: Error) => {
this.emit('error', err);
});
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MLLPServer doesn't handle the socket 'close' event. When a client disconnects unexpectedly, the server should clean up the per-socket buffer to prevent memory leaks. Consider adding a 'close' event handler in the connectionHandler to properly clean up resources.

Suggested change
});
});
socket.on('close', () => {
// Clear any remaining per-socket buffer data on disconnect.
buffer = Buffer.alloc(0);
});

Copilot uses AI. Check for mistakes.
{
"name": "nodehl7",
"version": "0.2.6",
"version": "1.1.0",
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version bump from 0.2.6 to 1.1.0 is inconsistent with semantic versioning. According to semver, this should be either 0.3.0 (as mentioned in the PR description) or 1.0.0 if it's considered a major release. The version 1.1.0 suggests this is the second minor release after 1.0.0, which hasn't occurred yet. The PR description states "Version bump to 0.3.0" but the actual version in package.json is 1.1.0, creating a discrepancy.

Suggested change
"version": "1.1.0",
"version": "0.3.0",

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants