Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
12 changes: 9 additions & 3 deletions boswatch/router/route.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,33 @@
by Bastian Schroll

@file: route.py
@date: 04.03.2019
@date: 30.08.2026
@author: Bastian Schroll
@description: Class for a single BOSWatch packet router route point
"""

import logging

logging.debug("- %s loaded", __name__)


class Route:
r"""!Class for single routing points"""
def __init__(self, name, callback, statsCallback=None, cleanupCallback=None):
def __init__(self, name, callback, statsCallback=None, cleanupCallback=None, isRouter=False):
r"""!Create a instance of an route point

@param name: name of the route point
@param callback: instance of the callback function
@param statsCallback: instance of the callback to get statistics (None)
@param cleanupCallback: instance of the callback to run a cleanup method (None)
@param isRouter: True if this route point delegates to another Router instance
(type: router in the config). Used by _process_route_recursive()
to distinguish a sub-router that filtered all packets internally
(parent should continue to the next route point) from a
module/plugin that explicitly stopped the route
(parent should stop immediately). (False)
"""
self.name = name
self.callback = callback
self.statistics = statsCallback
self.cleanup = cleanupCallback
self.isRouter = isRouter
87 changes: 72 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: 14.10.2026
@author: Bastian Schroll, Claus Schichl
@description: Class for the BOSWatch packet router
"""
import logging
Expand Down Expand Up @@ -51,32 +51,89 @@ 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
"""
# Catch incoming lists (e.g. from cascaded routers passing a list via runRouter)
if isinstance(bwPacket, list):
logging.debug("[%s] Received a list of %d packets. Branching immediately.", self.name, len(bwPacket))
return self._process_packet_list(bwPacket, start_index)

# Normal path for a single packet starts here
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:
if routeObject.isRouter:
# A nested router filtered/stopped internally for this packet.
# That's just an empty branch - the parent router continues normally.
logging.debug("[%s] sub-router '%s' returned False - continuing with next route point", self.name, routeObject.name)
continue

bwPacket = bwPacket_tmp
logging.debug("[%s] bwPacket returned", self.name)
logging.debug("[%s] finished", self.name)
# returning False stops the route immediately for this specific packet branch
logging.debug("[%s] stopped at route %s", self.name, routeObject.name)
return False

self._routerTime = time.time() - tmpTime
self._cumTime += self._routerTime
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))
# every packet runs through the rest of the route on its own - this finishes the route for all branches
return self._process_packet_list(bwPacket_tmp, i + 1)

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

return current_packet

def _process_packet_list(self, packets, start_index):
r"""!Process every packet of a list on its own from the given index

@param packets: list of packets
@param start_index: index of the routeList to start from
@return list of the remaining packets, or False if no packet is left
"""
results = []
for single_packet in packets:
res = self._process_route_recursive(single_packet, start_index)
if res is None or res is False:
# branch was stopped - nothing to collect
continue
if isinstance(res, list):
results.extend(res)
else:
results.append(res)
return results if results else False

def _getStatistics(self):
r"""!Returns statistical information's from last router run
Expand Down
4 changes: 2 additions & 2 deletions boswatch/router/routerManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
by Bastian Schroll

@file: routerManager.py
@date: 04.03.2019
@date: 30.08.2026
@author: Bastian Schroll
@description: Class for the BOSWatch packet router manager class
"""
Expand Down Expand Up @@ -89,7 +89,7 @@ def buildRouters(self, config):
loadedClass._cleanup))

elif routeType == "router":
routerDict_tmp[routerName].addRoute(Route(routeName, routerDict_tmp[routeRes].runRouter))
routerDict_tmp[routerName].addRoute(Route(routeName, routerDict_tmp[routeRes].runRouter, isRouter=True))

else:
logging.error("unknown type '%s' in %s", routeType, route)
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