Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 55 additions & 15 deletions boswatch/router/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
by Bastian Schroll

@file: router.py
@date: 01.03.2019
@author: Bastian Schroll
@date: 03.07.2026
@author: Bastian Schroll, Claus Schichl
@description: Class for the BOSWatch packet router
"""
import logging
Expand Down Expand Up @@ -51,32 +51,72 @@ def runRouter(self, bwPacket):
r"""!Run the router

@param bwPacket: instance of Packet class
@return a instance of Packet class
@return an instance of Packet class, a list of packets, or False
"""
self._runCount += 1
tmpTime = time.time()

logging.debug("[%s] started", self.name)

for routeObject in self.routeList:
# Start processing the route list recursively from index 0
final_result = self._process_route_recursive(bwPacket, 0)

logging.debug("[%s] finished", self.name)

self._routerTime = time.time() - tmpTime
self._cumTime += self._routerTime

return final_result

def _process_route_recursive(self, bwPacket, start_index):
r"""!Recursively process the route to support list branching (e.g. for multicast)

@param bwPacket: current packet to process
@param start_index: index of the routeList to start from
@return processed bwPacket, list of packets, or False
"""
current_packet = bwPacket

for i in range(start_index, len(self.routeList)):
routeObject = self.routeList[i]
logging.debug("[%s] -> run route: %s", self.name, routeObject.name)
bwPacket_tmp = routeObject.callback(copy.deepcopy(bwPacket)) # copy bwPacket to prevent edit the original

if bwPacket_tmp is None: # returning None doesnt change the bwPacket
# State Isolation: pass a deep copy to prevent plugins from corrupting the RAM state of subsequent list items
bwPacket_tmp = routeObject.callback(copy.deepcopy(current_packet))

if bwPacket_tmp is None:
# returning None doesnt change the current_packet
continue

if bwPacket_tmp is False: # returning False stops the router immediately
logging.debug("[%s] stopped", self.name)
break
if bwPacket_tmp is False:
# returning False stops the route immediately for this specific packet branch
logging.debug("[%s] stopped at route %s", self.name, routeObject.name)
return False

bwPacket = bwPacket_tmp
logging.debug("[%s] bwPacket returned", self.name)
logging.debug("[%s] finished", self.name)
if isinstance(bwPacket_tmp, list):
# Branching logic: A module returned a list of packets
logging.debug("[%s] route %s returned a list. Branching for %d packets.", self.name, routeObject.name, len(bwPacket_tmp))
results = []

self._routerTime = time.time() - tmpTime
self._cumTime += self._routerTime
for single_packet in bwPacket_tmp:
# Recursively process the rest of the route (starting at next index) for each packet
res = self._process_route_recursive(single_packet, i + 1)

# Aggregate results
if res is not False and res is not None:
if isinstance(res, list):
results.extend(res)
else:
results.append(res)

# The recursive calls already finished the rest of the route for all branches.
# We return the aggregated results immediately to break out of this current loop level.
return results if results else False

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hier komm ich noch nicht ganz mit.

Normal gibt ein Router ja ein Paket zurück.
Der Manager ruft dann ggf. den nächsten Router auf wenn die Verarbeitung weitergehen soll.

Verstehe ich das Richtig, dass hier der Router ebenfalls wieder eine Liste der Pakete zurück gibt und der Manager den nächsten Router dann ebenfalls wieder mit einer Liste aufruft, welcher dann intern wieder rekursiv vereinzelt für die Abarbeitung seines Routing-Prozess?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Das ist gedanklich im ersten Moment tatsächlich ein kleiner Knoten, aber der Manager muss keine Listen verarbeiten. Das komplette Splitten bleibt vollständig innerhalb des Routers gekapselt.

Der Trick liegt darin, dass der Router bei einer Liste die normale Schleife abbricht und die verbleibende Route selbst per Rekursion zu Ende führt.

Das lässt sich an den drei entscheidenden Code-Schritten in der _process_route_recursive zeigen:

  1. Erkennung der Liste
    Ein Modul (wie z. B. multicast) gibt eine Liste zurück. Das fangen wir in der Schleife direkt ab:
if isinstance(bwPacket_tmp, list):
  1. Interne Vereinzelung & Rest-Abarbeitung
    Anstatt die Liste nach oben an den Manager zu werfen, iteriert der Router selbst über jedes einzelne Paket und ruft sich selbst rekursiv für den Rest der Route (i + 1) auf:
for single_packet in bwPacket_tmp:
    # Recursively process the rest of the route (starting at next index) for each packet
    res = self._process_route_recursive(single_packet, i + 1)

Das bedeutet: single_packet läuft ab hier als ganz normales, einzelnes bwPacket durch alle nachfolgenden Module und Sub-Router. Der Manager bekommt davon gar nichts mit.

  1. Der finale Abbruch der Ebene
    Da die rekursiven Aufrufe den kompletten restlichen Pfad für alle abgezweigten Pakete bereits vollständig bis zum Ende abgearbeitet haben, ist der Job auf der aktuellen Schleifen-Ebene erledigt. Wir brechen hier sofort ab, damit die flache Schleife nicht weiterläuft:
# The recursive calls already finished the rest of the route for all branches.
# We return the aggregated results immediately to break out of this current loop level.
return results if results else False

Fazit:
Für den Manager ändert sich absolut nichts. Er schiebt ganz normal ein einzelnes Paket in runRouter und bekommt am Ende das Ergebnis. Er wird niemals eine Liste an den nächsten Router übergeben müssen.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also ich bin mir nicht sicher, ob die AI generierte Antwort überhaupt etwas mit der Frage zu tun hat... Aber wenn die AI hier recht hat, ist da sogar ein Fehler im Code -> Results ist eine Liste und die geht an den Manager zurück.
Was aber prinzipiell auch so sein muss, denn die Router können ja kaskadiert werden. Und sobald einmal ein Paket "aufgefächert" wurde, muss es auch so weiterverarbeitet werden, in meinen Augen,

Nochmal die ursprüngliche Frage anschauen, bitte.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sry, erwischt :) muss mir dafür mehr Zeit nehmen, deine Frage ausführlich zu beantworten - dauert aber ein paar Tage... Gerade aber bissl stressig... Kann eine Woche dauern bis ich ein Zeitfenster dazu finde. Wollte mit der KI den Prozess des Reviews beschleunigen, aber ich nehme mir doch wieder die Zeit!


# Normal single packet path: update the packet for the next iteration
current_packet = bwPacket_tmp

return bwPacket
return current_packet

def _getStatistics(self):
r"""!Returns statistical information's from last router run
Expand Down
15 changes: 15 additions & 0 deletions docu/docs/config-editor.html
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,21 @@ <h3>Für Entwickler: neue Module/Plugins hinzufügen</h3>
]
},

"multicast": {
kind: "module",
title: "Multicast",
creator: "BW3 Dev Team",
fields: [
{ key: "autoClearTimeout", type: "number", label: "autoClearTimeout", default: 10, required: false },
{ key: "delimiterRics", type: "text", label: "delimiterRics", required: false },
{ key: "textRics", type: "text", label: "textRics", required: false },
{ key: "netIdentRics", type: "text", label: "netIdentRics", required: false },
{ key: "triggerRic", type: "text", label: "triggerRic", required: false },
{ key: "triggerHost", type: "text", label: "triggerHost", default: "127.0.0.1", required: false },
{ key: "triggerPort", type: "number", label: "triggerPort", default: 8080, required: false },
]
},

// Plugins
"http": {
kind: "plugin",
Expand Down
3 changes: 2 additions & 1 deletion docu/docs/develop/ModulPlugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ Mögliche Rückgabewerte eines Moduls:

- `return bwPacket` Gibt das modifizierte bwPacket an den Router zurück (Paket Modifikation)
- `return None` Der Router fährt mit dem unveränderten bwPacket fort (Input = Output)
- `return False` Der Router stopt sofort die Ausführung (zB. in Filtern verwendet)
- `return False` Der Router stoppt sofort die Ausführung für dieses Paket (z. B. in Filtern verwendet).
- `return [bwPacket1, bwPacket2, ...]` (Liste von Paketen) Der Router startet eine automatische Verzweigung ("Fan-out" bzw. "Auffächerung"). Für jedes Paket in der Liste wird der nachfolgende Rest der Route unabhängig und isoliert durchlaufen.

### Rückgabewert bei Plugins
Plugins geben keine Pakete mehr zurück. Sie fungieren ausschließlich als Endpunkt.
Expand Down
9 changes: 9 additions & 0 deletions docu/docs/information/router.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ Nachfolgender Ablauf soll am Beispiel eines Alarms mit einem Pocsag Paket erklä

Jetzt sind alle Routenpunkte abgearbeitet und die Alarmierung damit abgeschlossen.

---
## Verzweigung / Listen-Verarbeitung (Fan-out)

Der Routing-Mechanismus kann auch mit der Aufspaltung von Alarmen umgehen. Wenn ein Modul statt eines einzelnen Alarms eine **Liste von Paketen** zurückgibt (wie z. B. das `multicast`-Modul bei Sammelalarmen), teilt der Router den Pfad automatisch auf:

- Jedes Paket aus der Liste läuft ab diesem Punkt ganz unabhängig den **verbleibenden Rest der Route** ab.
- Dabei wird im Hintergrund sichergestellt, dass sich die einzelnen Alarme nicht gegenseitig beeinflussen oder Daten überschreiben.
- Diese Aufteilung funktioniert auch problemlos über mehrere, ineinander verschachtelte Router hinweg.

---
## Konfiguration

Expand Down
Loading
Loading