====== 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 =====
* ''std.alloc''
----
===== 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