Initial public release
Full BigBrother network implant - passive SOC + active exploitation. Personal identifiers removed; all capabilities intact. See README.md for setup and docs/deployment.md for detailed deployment.
This commit is contained in:
+185
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Multiprocess-safe event bus with pub/sub pattern.
|
||||
|
||||
Uses multiprocessing.Queue for cross-process event delivery.
|
||||
A dedicated dispatcher thread reads from the queue and fans out
|
||||
to registered subscriber callbacks.
|
||||
"""
|
||||
|
||||
import time
|
||||
import threading
|
||||
import logging
|
||||
import multiprocessing
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from typing import Callable, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Canonical event types
|
||||
EVENT_TYPES = frozenset([
|
||||
"CREDENTIAL_FOUND", "HOST_DISCOVERED", "PCAP_ROTATED",
|
||||
"MODULE_STARTED", "MODULE_STOPPED", "MODULE_ERROR",
|
||||
"RESOURCE_WARNING", "RESOURCE_CRITICAL", "KILL_SWITCH",
|
||||
"CONNECTIVITY_CHANGED", "VLAN_DETECTED", "HANDSHAKE_CAPTURED",
|
||||
"TICKET_HARVESTED", "CLOUD_TOKEN_FOUND", "BEACON_DETECTED",
|
||||
"CHANGE_DETECTED", "THERMAL_WARNING", "SCOPE_VIOLATION",
|
||||
"TOOL_CRASHED", "TOOL_RESTARTED",
|
||||
])
|
||||
|
||||
|
||||
@dataclass
|
||||
class Event:
|
||||
"""Single event on the bus."""
|
||||
event_type: str
|
||||
payload: dict
|
||||
timestamp: float = field(default_factory=time.time)
|
||||
source_module: str = ""
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
class EventBus:
|
||||
"""Multiprocess-safe publish/subscribe event bus.
|
||||
|
||||
Subscribers register a callback with an optional event_type filter.
|
||||
Publishing pushes an Event into a shared mp.Queue. A background
|
||||
dispatcher thread reads from the queue and invokes matching callbacks.
|
||||
"""
|
||||
|
||||
def __init__(self, maxsize: int = 10000):
|
||||
self._queue: multiprocessing.Queue = multiprocessing.Queue(maxsize=maxsize)
|
||||
self._subscribers: dict[str, list[Callable]] = {} # event_type -> [callbacks]
|
||||
self._wildcard_subscribers: list[Callable] = [] # receive all events
|
||||
self._lock = threading.Lock()
|
||||
self._dispatcher_thread: Optional[threading.Thread] = None
|
||||
self._running = False
|
||||
self._event_count = 0
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Lifecycle
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def start(self) -> None:
|
||||
"""Start the dispatcher thread."""
|
||||
if self._running:
|
||||
return
|
||||
self._running = True
|
||||
self._dispatcher_thread = threading.Thread(
|
||||
target=self._dispatch_loop, daemon=True, name="sensor-bus-dispatch"
|
||||
)
|
||||
self._dispatcher_thread.start()
|
||||
logger.info("EventBus dispatcher started")
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Stop the dispatcher thread. Drains remaining events first."""
|
||||
if not self._running:
|
||||
return
|
||||
self._running = False
|
||||
# Push sentinel to unblock queue.get()
|
||||
try:
|
||||
self._queue.put_nowait(None)
|
||||
except Exception:
|
||||
pass
|
||||
if self._dispatcher_thread and self._dispatcher_thread.is_alive():
|
||||
self._dispatcher_thread.join(timeout=5.0)
|
||||
logger.info("EventBus dispatcher stopped (dispatched %d events)", self._event_count)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Pub / Sub
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def subscribe(self, callback: Callable, event_type: Optional[str] = None) -> None:
|
||||
"""Register a callback for events.
|
||||
|
||||
Args:
|
||||
callback: Callable that takes a single Event argument.
|
||||
event_type: If set, only events of this type are delivered.
|
||||
If None, the callback receives ALL events.
|
||||
"""
|
||||
with self._lock:
|
||||
if event_type is None:
|
||||
self._wildcard_subscribers.append(callback)
|
||||
else:
|
||||
self._subscribers.setdefault(event_type, []).append(callback)
|
||||
|
||||
def unsubscribe(self, callback: Callable, event_type: Optional[str] = None) -> None:
|
||||
"""Remove a previously registered callback."""
|
||||
with self._lock:
|
||||
if event_type is None:
|
||||
try:
|
||||
self._wildcard_subscribers.remove(callback)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
subs = self._subscribers.get(event_type, [])
|
||||
try:
|
||||
subs.remove(callback)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def publish(self, event: Event) -> None:
|
||||
"""Publish an event onto the bus (multiprocess-safe).
|
||||
|
||||
Non-blocking: if the queue is full the event is dropped and a
|
||||
warning is logged.
|
||||
"""
|
||||
try:
|
||||
self._queue.put_nowait(event)
|
||||
except Exception:
|
||||
logger.warning("EventBus queue full — dropped %s from %s",
|
||||
event.event_type, event.source_module)
|
||||
|
||||
def emit(self, event_type: str, payload: dict, source_module: str = "") -> None:
|
||||
"""Convenience wrapper: build an Event and publish it."""
|
||||
self.publish(Event(
|
||||
event_type=event_type,
|
||||
payload=payload,
|
||||
source_module=source_module,
|
||||
))
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Internal dispatcher
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _dispatch_loop(self) -> None:
|
||||
"""Background loop: pull events from the mp.Queue, fan out to callbacks."""
|
||||
while self._running:
|
||||
try:
|
||||
event = self._queue.get(timeout=0.5)
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if event is None: # sentinel
|
||||
continue
|
||||
|
||||
self._event_count += 1
|
||||
|
||||
with self._lock:
|
||||
targets = list(self._wildcard_subscribers)
|
||||
targets.extend(self._subscribers.get(event.event_type, []))
|
||||
|
||||
for cb in targets:
|
||||
try:
|
||||
cb(event)
|
||||
except Exception:
|
||||
logger.exception("Subscriber callback error for %s", event.event_type)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Introspection
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@property
|
||||
def event_count(self) -> int:
|
||||
return self._event_count
|
||||
|
||||
@property
|
||||
def queue_size(self) -> int:
|
||||
try:
|
||||
return self._queue.qsize()
|
||||
except NotImplementedError:
|
||||
return -1
|
||||
|
||||
def get_queue(self) -> multiprocessing.Queue:
|
||||
"""Return the underlying mp.Queue for sharing with child processes."""
|
||||
return self._queue
|
||||
Reference in New Issue
Block a user