std.hl7.core
MLLP-Foundation und MSH/ACK-Engine — das Fundament aller std.hl7.*-Units. Implementiert den MLLP-TCP-Framing-Protokoll (VT+Nachricht+FS+CR), den MSH-Segment-Parser, ACK-Erzeugung, versionsübergreifende Validierung und partnerbasierte Duplikatserkennung per FNV1a-Hash.
Alle anderen HL7-Units importieren std.hl7.core und setzen die hier definierten Konstanten und Structs voraus.
import std.hl7.core;
import std.alloc;
// State für Duplikatserkennung allozieren und initialisieren
var state: int64 := alloc(HL7_STATE_SIZE);
Hl7StateInit(state);
// Eingehende MLLP-Nachricht aus TCP-Puffer extrahieren
var tcpBuf: int64 := alloc(8192);
var msgBuf: int64 := alloc(8192);
var msgLen: int64 := MllpFrameRead(tcpBuf, tcpBufLen, msgBuf, 8192);
// MSH parsen
var msh: int64 := alloc(HL7_MSH_SIZE);
var rc: int64 := Hl7MshParse(msgBuf, msgLen, msh);
// ACK senden
var ackBuf: int64 := alloc(512);
var ackLen: int64 := Hl7AckWrite(msh, "AA"c as int64, "OK"c as int64, ackBuf, 512);
// MLLP-Frame ums ACK legen
var outBuf: int64 := alloc(600);
var outLen: int64 := MllpFrameWrite(outBuf, 600, ackBuf, ackLen);
// outBuf an Socket senden ...
Imports
Konstanten
Fehlercodes
| Konstante | Wert | Bedeutung |
HL7_OK | 0 | Kein Fehler |
HL7_ERR_TRUNC | 1 | Ausgabepuffer zu klein |
HL7_ERR_SYNTAX | 2 | MSH-Syntaxfehler |
HL7_ERR_DUP | 3 | Duplikat-Nachrichten-ID erkannt |
HL7_ERR_OVERFLOW | 4 | Zu viele Partner-Slots belegt |
HL7_ERR_VERSION | 5 | HL7-Version nicht unterstützt |
MLLP-Rahmenbytes
| Konstante | Wert | Bedeutung |
HL7_MLLP_VT | 11 (0x0B) | Vertical Tab — Framestart |
HL7_MLLP_FS | 28 (0x1C) | File Separator — Frameende |
HL7_MLLP_CR | 13 (0x0D) | Carriage Return — nach FS |
ACK-Codes
| Konstante | Wert | Bedeutung |
HL7_ACK_AA | 1 | Application Accept — Nachricht verarbeitet |
HL7_ACK_AE | 2 | Application Error — Verarbeitungsfehler |
HL7_ACK_AR | 3 | Application Reject — Nachricht abgelehnt |
Structs
Alle Structs werden vom Caller alloziert. Größenkonstanten geben die benötigte Byte-Anzahl an.
Hl7Msh — MSH-Struct (HL7_MSH_SIZE = 192 Bytes)
Jedes Feld wird als Zeiger+Länge-Paar gespeichert (je 8 Bytes). Alle Zeiger weisen in den Originalnachrichtenpuffer (Zero-Copy).
| Offset-Konstante | Offset | HL7-Feld | Inhalt |
HL7_MSH_SEND_APP / _LEN | 0 / 8 | MSH-3 | Sendende Applikation |
HL7_MSH_SEND_FAC / _LEN | 16 / 24 | MSH-4 | Sendende Einrichtung |
HL7_MSH_RECV_APP / _LEN | 32 / 40 | MSH-5 | Empfangende Applikation |
HL7_MSH_RECV_FAC / _LEN | 48 / 56 | MSH-6 | Empfangende Einrichtung |
HL7_MSH_DATETIME / _LEN | 64 / 72 | MSH-7 | Nachrichtenzeit (YYYYMMDDHHmmss) |
HL7_MSH_MSG_TYPE / _LEN | 80 / 88 | MSH-9.1 | Nachrichtentyp (ADT, ORM, ORU …) |
HL7_MSH_TRIGGER / _LEN | 96 / 104 | MSH-9.2 | Trigger-Event (A01, O01, R01 …) |
HL7_MSH_CTRL_ID / _LEN | 112 / 120 | MSH-10 | Nachrichten-Kontroll-ID |
HL7_MSH_PROC_ID / _LEN | 128 / 136 | MSH-11 | Processing ID (P=Produktion, T=Test) |
HL7_MSH_VERSION / _LEN | 144 / 152 | MSH-12 | HL7-Version (z. B. „2.5.1„) |
HL7_MSH_CHARSET / _LEN | 160 / 168 | MSH-18 | Zeichensatz (leer = ISO-8859-1) |
HL7_MSH_FIELD_SEP | 176 | — | Ermittelter Feldseparator (ASCII-Code) |
HL7_MSH_COMP_SEP | 184 | — | Ermittelter Komponentenseparator |
Hl7State — Dedup-State (HL7_STATE_SIZE = 8448 Bytes)
Speichert pro Partner (max. HL7_MAX_PARTNERS = 16) einen Ring-Buffer mit HL7_DUP_RING = 64 FNV1a-Hashes der Kontroll-IDs zur Duplikatserkennung.
| Konstante | Wert | Bedeutung |
HL7_MAX_PARTNERS | 16 | Maximale Anzahl gleichzeitiger Partner |
HL7_DUP_RING | 64 | Ring-Buffer-Größe pro Partner |
HL7_SLOT_SIZE | 528 | Bytes pro Partner-Slot |
HL7_SLOT_KEY | 0 | Partner-Key (int64) im Slot |
HL7_SLOT_RPOS | 8 | Aktueller Ring-Position-Index |
HL7_SLOT_RING | 16 | Beginn des Hash-Ring-Buffers |
Funktionen
Initialisierung
| Signatur | Beschreibung |
Hl7StateInit(state: int64): void | Nullt den 8448-Byte Dedup-State; muss vor dem ersten Hl7DupCheck-Aufruf aufgerufen werden |
MLLP-Framing
| Signatur | Beschreibung |
MllpFrameWrite(buf: int64, bufMax: int64, msg: int64, msgLen: int64): int64 | Schreibt VT + msg + FS + CR nach buf; gibt geschriebene Bytes zurück oder -1 bei Puffer-Overflow |
MllpFrameRead(buf: int64, bufLen: int64, out: int64, outMax: int64): int64 | Extrahiert HL7-Nachricht aus MLLP-Frame; gibt Nachrichtenlänge zurück; -1 kein Frame gefunden, -2 out zu klein |
MSH parsen & ACK schreiben
| Signatur | Beschreibung |
Hl7MshParse(msg: int64, len: int64, msh: int64): int64 | Parst MSH-Segment; füllt msh (HL7_MSH_SIZE); Feldseparator aus MSH-1, Komponentenseparator aus MSH-2.1; gibt HL7_OK oder HL7_ERR_SYNTAX |
Hl7AckWrite(msh: int64, code: int64, text: int64, out: int64, outMax: int64): int64 | Erzeugt ACK-Nachricht; tauscht Sender/Empfänger aus MSH; code = 2-Byte-pchar-Literal („AA“c, „AE“c oder „AR“c — als int64); text null-terminiert; MSH-10 als Platzhalter „ACK“; gibt Bytes zurück |
Duplikatserkennung & Validierung
| Signatur | Beschreibung |
Hl7DupCheck(state: int64, partnerKey: int64, ctrlId: int64): int64 | 1 = Duplikat, 0 = neue Nachricht; FNV1a-Hash der Kontroll-ID; Slot-Suche via partnerKey-Hash; neuer Eintrag wird automatisch im Ring eingetragen |
Hl7VersionCheck(msh: int64): int64 | HL7_OK wenn MSH-12 mit „2.„ beginnt und Version bekannt (2.3.1, 2.4, 2.5, 2.5.1, 2.6, 2.7); sonst HL7_ERR_VERSION |
Hl7IsTestMsg(msh: int64): int64 | 1 wenn MSH-11 (PROC_ID) mit 'T' beginnt — Testnachrichten dürfen NICHT in die Produktionsdatenbank |
Hl7IsUtf8(msh: int64): int64 | 1 wenn MSH-18 mit „UNICODE“ beginnt; 0 = Standard ISO-8859-1 |
Codebeispiel — Vollständige MLLP-Server-Iteration
import std.hl7.core;
import std.alloc;
fn processHL7Message(tcpBuf: int64, tcpLen: int64, state: int64, sockFd: int64) {
var msgBuf: int64 := alloc(65536);
var msgLen: int64 := MllpFrameRead(tcpBuf, tcpLen, msgBuf, 65536);
if (msgLen < 0) {
free(msgBuf, 65536);
return;
}
var msh: int64 := alloc(HL7_MSH_SIZE);
if (Hl7MshParse(msgBuf, msgLen, msh) != HL7_OK) {
free(msh, HL7_MSH_SIZE);
free(msgBuf, 65536);
return;
}
// Versionsprüfung
if (Hl7VersionCheck(msh) != HL7_OK) {
var ackBuf: int64 := alloc(512);
var ackLen: int64 := Hl7AckWrite(msh, "AR"c as int64,
"Unsupported HL7 version"c as int64, ackBuf, 512);
var outBuf: int64 := alloc(600);
var outLen: int64 := MllpFrameWrite(outBuf, 600, ackBuf, ackLen);
// TcpWrite(sockFd, outBuf, outLen);
free(outBuf, 600);
free(ackBuf, 512);
free(msh, HL7_MSH_SIZE);
free(msgBuf, 65536);
return;
}
// Testnachricht abfangen
if (Hl7IsTestMsg(msh) == 1) {
// Logging, aber nicht in Produktions-DB schreiben
}
// Duplikatsprüfung (partnerKey = Hash der Absenderkennung)
var ctrlId: int64 := peek64(msh + HL7_MSH_CTRL_ID);
if (Hl7DupCheck(state, 1234, ctrlId) == 1) {
// Duplikat — ACK AA trotzdem senden (Sender erwartet Quittung)
}
// Normale Verarbeitung ...
var ackBuf: int64 := alloc(512);
var ackLen: int64 := Hl7AckWrite(msh, "AA"c as int64, "OK"c as int64, ackBuf, 512);
var outBuf: int64 := alloc(600);
MllpFrameWrite(outBuf, 600, ackBuf, ackLen);
free(outBuf, 600);
free(ackBuf, 512);
free(msh, HL7_MSH_SIZE);
free(msgBuf, 65536);
}
Hinweise
Zero-Copy-Parsing: Hl7MshParse setzt alle Zeiger im MSH-Struct direkt in den Originalnachrichtenpuffer — keine Kopien. Der Puffer muss bis zur letzten Verwendung des MSH-Structs gültig bleiben.
Caller alloziert: Alle Structs (MSH, State) werden vom Caller alloziert. Die Größenkonstanten (HL7_*_SIZE) geben die benötigte Byte-Anzahl an.
Standard-Zeichensatz: HL7 v2 verwendet standardmäßig ISO-8859-1, nicht UTF-8. Hl7IsUtf8 prüft MSH-18 auf das „UNICODE„-Präfix — nur dann ist UTF-8 zulässig.
Testnachrichten: Hl7IsTestMsg erkennt MSH-11 = 'T'. Testnachrichten müssen quittiert werden, dürfen aber nicht in Produktionsdatenbanken geschrieben werden.
Duplikatserkennung: Ring-Buffer mit 64 Einträgen pro Partner. Bei mehr als 64 unterschiedlichen Kontroll-IDs pro Partner kann eine alte ID erneut als „neu“ erscheinen.
ACK-Sequenz: Der MLLP-Standard erfordert immer eine ACK-Antwort, auch bei Fehlern (AE/AR). Fehlende ACKs führen beim Sender zu Timeouts und Wiederholungsversuchen.
Quelldatei
| Unit | Datei |
std.hl7.core | std/hl7/core.lyx |
Letzte Aktualisierung: 2026-06-16