Zurück zur Startseite

Rückentwicklung des BCD4-Warteschlangenanzeigeprotokolls

Der Artikel beschreibt das Reverse Engineering eines proprietären elektronischen Warteschlangenanzeigeprotokolls mit BCD4-Kodierung und Carry in der Prüfsumme. Paketstruktur, Python-Code zum Zusammenbauen und Integration in Web-System über HTTP und COM-Port werden bereitgestellt. Nützlich für die Arbeit mit Legacy-Equipment.

BCD4 und Carry in Warteschlangenanzeige rückwärts
Advertisement 728x90

Reverse Engineering eines Warteschlangenanzeige-Protokolls: BCD4-Kodierung und Carry-Arithmetik

Ein Ingenieur hat ein proprietäres Warteschlangenanzeige-Protokoll ohne Dokumentation reverse-engineered. Das Protokoll verwendet BCD4-Kodierung, wobei jedes Datenbit ein volles Byte belegt, um Rauschimmunität auf langen RS-232-Leitungen bei 1200 Baud zu gewährleisten. Die Paketstruktur umfasst 33 Bytes mit einem Marker, Zahlziffern, Fensternummer und Prüfsummenfeldern mit Carry.

BCD4: Kodierung für zuverlässige Übertragung

Das Protokoll nutzt eine Hardware-Methode zur Rauschunterdrückung: Jedes Bit einer Zahl (0–15) wird als Byte kodiert. 0x7F steht für 1 (sieben Einsen für einen klaren Impuls), 0x00 steht für 0. Dies streckt einen 4-Bit-Wert auf 4 Bytes.

Kodierungsbeispiele:

Google AdInline article slot

| Zahl | Bits | BCD4-Bytes |

|------|--------|------------------|

| 0 | 0000 | 00 00 00 00 |

Google AdInline article slot

| 1 | 0001 | 7F 00 00 00 |

| 2 | 0010 | 00 7F 00 00 |

| 5 | 0101 | 7F 00 7F 00 |

Google AdInline article slot

| 9 | 1001 | 7F 00 00 7F |

| 12 | 1100 | 00 00 7F 7F |

Implementierung in Python:

def _bcd4(value: int) -> bytes:
    return bytes([0x7F if value & (1 << i) else 0x00 for i in range(4)])

print(_bcd4(5).hex(' '))  # 7f 00 7f 00
print(_bcd4(9).hex(' '))  # 7f 00 00 7f

Die Baudrate von 1200 Baud sorgt für Zuverlässigkeit auf Kosten des Durchsatzes.

Paketstruktur für die Zahlenanzeige

Ein 33-Byte-Paket zeigt eine Zahl in einem Fenster an:

  • Byte 0: 0x55 — Startmarker.
  • Bytes 1–4: Modus-Header.
  • Bytes 5–8: BCD4(Einerziffer).
  • Bytes 9–12: BCD4(Zehnerziffer).
  • Bytes 13–16: BCD4(Hunderterziffer).
  • Bytes 17–20: BCD4(Fenster-Einerziffer).
  • Bytes 21–24: BCD4(Fenster-Zehnerziffer).
  • Bytes 25–28: BCD4(c1) — unterer Prüfsummenteil.
  • Bytes 29–32: BCD4(c2) — oberer Teil mit Carry.

Beispiel für Zahl 487 in Fenster 19:

55 00 7F 00 00          # Header
7F 7F 7F 00             # BCD4(7)
00 00 00 7F             # BCD4(8)
00 00 7F 00             # BCD4(4)
7F 00 00 7F             # BCD4(9)
7F 00 00 00             # BCD4(1)
[Prüfsummenfelder]

Prüfsummenberechnung mit Carry

Die Prüfsummenfelder c1 und c2 bilden einen 8-Bit-Wert mit Carry-Mechanik:

  • Berechne raw_c1 als lineare Kombination der Paketfelder.
  • Wenn raw_c1 >= 16: Carry = 1, c1 = raw_c1 - 16.
  • Sonst: Carry = 0, c1 = raw_c1.
  • c2 wird mit Carry aus anderen Feldern berechnet.

Carry tritt bei Zahlen wie 80–90 abhängig vom Fenster auf. KI (Claude) identifizierte das Muster aus 30 Paketbeispielen nach Verfeinerung der Hypothese: zwei 4-Bit-Nibbles mit Carry wie in der Arithmetik.

Im "Alle Fenster"-Modus (25 Bytes) gibt es keinen Carry, und die Formel ist einfacher.

Der Fensterprogrammiermodus verwendet zwei unabhängige Carry-Ketten mit Multiplikation und Konstanten:

  • Kette A: raw_A aus W_lo, carry_A = raw_A >> 4, field_A1 = raw_A & 0xF, field_A2 aus W_hi und carry_A.
  • Kette B: ähnlich für eine andere lineare Funktion.

Zusammenbau und Senden des Pakets

Paketzusammenbau-Funktion:

def build_window_packet(window: int, number: int) -> bytes:
    d1 = number % 10
    d2 = (number // 10) % 10
    d3 = (number // 100) % 10
    w_lo = window % 10
    w_hi = window // 10

    c1, c2 = _checksum(d1, d2, d3, w_lo, w_hi)

    packet = bytearray(b'\x55\x00\x7F\x00\x00')
    packet += _bcd4(d1) + _bcd4(d2) + _bcd4(d3)
    packet += _bcd4(w_lo) + _bcd4(w_hi)
    packet += _bcd4(c1) + _bcd4(c2)

    assert len(packet) == 33
    return bytes(packet)

Das Paket wird an einen COM-Port gesendet.

Integration in ein Web-System

Ein Python-Server akzeptiert HTTP POST:

POST / {"window": 5, "ticket": "487"}

Er parst JSON, baut das Paket und schreibt auf den Port. Ein Heartbeat-Mechanismus stellt die Verbindung wieder her:

  • 60-Sekunden-Intervall bei OK.
  • 10 Sekunden bei Fehler.
def _heartbeat_loop():
    while True:
        ok = _try_register(...)
        was = _server_state['connected']
        _server_state['connected'] = ok
        if not was and ok:
            print("[OK] Verbindung wiederhergestellt")
        elif was and not ok:
            print("[WARN] Verbindung verloren")
        time.sleep(60 if ok else 10)

Wichtige Erkenntnisse

  • BCD4-Kodierung streckt 1 Bit auf ein Byte für Rauschimmunität bei 1200 Baud.
  • Die Prüfsumme verwendet Carry zwischen Nibbles c1/c2.
  • Reverse Engineering mit KI ist effektiv mit präzisen Hypothesen und Paketbeispielen.
  • Der Server ermöglicht nahtlose Integration von Legacy-Geräten ins Web.
  • Protokoll: 33 Bytes, Marker 0x55, Ziffern + Fenster + Prüfsumme.

— Editorial Team

Advertisement 728x90

Weiterlesen