Crypto: - Replace broken ChaCha20 (static nonce) with ChaCha20-Poly1305 AEAD - HKDF-SHA256 key derivation from per-device factory NVS master keys - Random 12-byte nonce per message (ESP32 hardware RNG) - crypto_init/encrypt/decrypt API with mbedtls legacy (ESP-IDF v5.3.2) - Custom partition table with factory NVS (fctry at 0x10000) Firmware: - crypto.c full rewrite, messages.c device_id prefix + AEAD encrypt - crypto_init() at boot with esp_restart() on failure - Fix command_t initializations across all modules (sub/help fields) - Clean CMakeLists dependencies for ESP-IDF v5.3.2 C3PO (C2): - Rename tools/c2 + tools/c3po -> tools/C3PO - Per-device CryptoContext with HKDF key derivation - KeyStore (keys.json) for master key management - Transport parses device_id:base64(...) wire format Tools: - New tools/provisioning/provision.py for factory NVS key generation - Updated flasher with mbedtls config for v5.3.2 Docs: - Update all READMEs for new crypto, C3PO paths, provisioning - Update roadmap, architecture diagrams, security sections - Update CONTRIBUTING.md project structure
135 lines
4.1 KiB
Python
135 lines
4.1 KiB
Python
"""Main camera server combining UDP receiver and unified web server."""
|
|
|
|
from typing import Optional, Callable
|
|
|
|
from .config import (
|
|
UDP_HOST, UDP_PORT, WEB_HOST, WEB_PORT, IMAGE_DIR,
|
|
DEFAULT_USERNAME, DEFAULT_PASSWORD, FLASK_SECRET_KEY, MULTILAT_AUTH_TOKEN
|
|
)
|
|
from .udp_receiver import UDPReceiver
|
|
from web.server import UnifiedWebServer
|
|
from web.mlat import MlatEngine
|
|
|
|
|
|
class CameraServer:
|
|
"""
|
|
Combined camera server that manages both:
|
|
- UDP receiver for incoming camera frames from ESP devices
|
|
- Unified web server for dashboard, cameras, and trilateration
|
|
"""
|
|
|
|
def __init__(self,
|
|
udp_host: str = UDP_HOST,
|
|
udp_port: int = UDP_PORT,
|
|
web_host: str = WEB_HOST,
|
|
web_port: int = WEB_PORT,
|
|
image_dir: str = IMAGE_DIR,
|
|
username: str = DEFAULT_USERNAME,
|
|
password: str = DEFAULT_PASSWORD,
|
|
device_registry=None,
|
|
on_frame: Optional[Callable] = None):
|
|
"""
|
|
Initialize the camera server.
|
|
|
|
Args:
|
|
udp_host: Host to bind UDP receiver
|
|
udp_port: Port for UDP receiver
|
|
web_host: Host to bind web server
|
|
web_port: Port for web server
|
|
image_dir: Directory to store camera frames
|
|
username: Web interface username
|
|
password: Web interface password
|
|
device_registry: DeviceRegistry instance for device listing
|
|
on_frame: Optional callback when frame is received (camera_id, frame, addr)
|
|
"""
|
|
self.mlat_engine = MlatEngine()
|
|
|
|
self.udp_receiver = UDPReceiver(
|
|
host=udp_host,
|
|
port=udp_port,
|
|
image_dir=image_dir,
|
|
on_frame=on_frame
|
|
)
|
|
|
|
self.web_server = UnifiedWebServer(
|
|
host=web_host,
|
|
port=web_port,
|
|
image_dir=image_dir,
|
|
username=username,
|
|
password=password,
|
|
secret_key=FLASK_SECRET_KEY,
|
|
multilat_token=MULTILAT_AUTH_TOKEN,
|
|
device_registry=device_registry,
|
|
mlat_engine=self.mlat_engine
|
|
)
|
|
|
|
@property
|
|
def is_running(self) -> bool:
|
|
"""Check if both servers are running."""
|
|
return self.udp_receiver.is_running and self.web_server.is_running
|
|
|
|
@property
|
|
def udp_running(self) -> bool:
|
|
return self.udp_receiver.is_running
|
|
|
|
@property
|
|
def web_running(self) -> bool:
|
|
return self.web_server.is_running
|
|
|
|
def start(self) -> dict:
|
|
"""
|
|
Start both UDP receiver and web server.
|
|
|
|
Returns:
|
|
dict with status of each server
|
|
"""
|
|
results = {
|
|
"udp": {"started": False, "host": self.udp_receiver.host, "port": self.udp_receiver.port},
|
|
"web": {"started": False, "host": self.web_server.host, "port": self.web_server.port}
|
|
}
|
|
|
|
if self.udp_receiver.start():
|
|
results["udp"]["started"] = True
|
|
|
|
if self.web_server.start():
|
|
results["web"]["started"] = True
|
|
results["web"]["url"] = self.web_server.get_url()
|
|
|
|
return results
|
|
|
|
def stop(self) -> dict:
|
|
"""
|
|
Stop both servers.
|
|
|
|
Returns:
|
|
dict with stop status
|
|
"""
|
|
self.udp_receiver.stop()
|
|
self.web_server.stop()
|
|
|
|
return {
|
|
"udp": {"stopped": True},
|
|
"web": {"stopped": True}
|
|
}
|
|
|
|
def get_status(self) -> dict:
|
|
"""Get status of both servers."""
|
|
return {
|
|
"udp": {
|
|
"running": self.udp_receiver.is_running,
|
|
"host": self.udp_receiver.host,
|
|
"port": self.udp_receiver.port,
|
|
**self.udp_receiver.get_stats()
|
|
},
|
|
"web": {
|
|
"running": self.web_server.is_running,
|
|
"host": self.web_server.host,
|
|
"port": self.web_server.port,
|
|
"url": self.web_server.get_url() if self.web_server.is_running else None
|
|
}
|
|
}
|
|
|
|
def get_active_cameras(self) -> list:
|
|
"""Get list of active camera IDs."""
|
|
return self.udp_receiver.active_cameras
|