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