====== 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