espilon-source/tools/C3PO/streams/server.py
Eun0us 8b6c1cd53d ε - ChaCha20-Poly1305 AEAD + HKDF crypto upgrade + C3PO rewrite + docs
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
2026-02-10 21:28:45 +01:00

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