mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-04-23 05:06:38 +00:00
fix: post-audit — runtime bugs, functional gaps, docs, hardening
Phase A — Runtime bugs: - SSE: add push_event() method to SSEEventStreamer (was missing, masked by mocks) - MAVLink: satellites_visible=10 (was 0, triggers ArduPilot failsafe) - MAVLink: horiz_accuracy=sqrt(P[0,0]+P[1,1]) per spec (was sqrt(avg)) - MAVLink: MEDIUM confidence → fix_type=3 per solution.md (was 2) Phase B — Functional gaps: - handle_user_fix() injects operator GPS into ESKF with noise=500m - app.py uses create_vo_backend() factory (was hardcoded SequentialVO) - ESKF: Mahalanobis gating on satellite updates (rejects outliers >5σ) - ESKF: public accessors (position, quaternion, covariance, last_timestamp) - Processor: no more private ESKF field access Phase C — Documentation: - README: correct API endpoints, CLI command, 40+ env vars documented - Dockerfile: ENV prefixes match pydantic-settings (DB_, SATELLITE_, MAVLINK_) - tech_stack.md marked ARCHIVED (contradicts solution.md) Phase D — Hardening: - JWT auth middleware (AUTH_ENABLED=false default, verify_token on /flights) - TLS config env vars (AUTH_SSL_CERTFILE, AUTH_SSL_KEYFILE) - SHA-256 tile manifest verification in SatelliteDataManager - AuthConfig, ESKFSettings, MAVLinkConfig, SatelliteConfig in config.py Also: conftest.py shared fixtures, download_tiles.py, convert_to_trt.py scripts, config wiring into app.py lifespan, config-driven ESKF, calculate_precise_angle fix. Tests: 196 passed / 8 skipped. Ruff clean. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,8 @@ SAT-01: Reads pre-loaded tiles from a local z/x/y directory (no live HTTP during
|
||||
SAT-02: Tile selection uses ESKF position ± 3σ_horizontal to define search area.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
@@ -26,6 +28,8 @@ class SatelliteDataManager:
|
||||
downloads and stores tiles before the mission.
|
||||
"""
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tile_dir: str = ".satellite_tiles",
|
||||
@@ -37,11 +41,47 @@ class SatelliteDataManager:
|
||||
# In-memory LRU for hot tiles (avoids repeated disk reads)
|
||||
self._mem_cache: dict[str, np.ndarray] = {}
|
||||
self._mem_cache_max = 256
|
||||
# SHA-256 manifest for tile integrity (якщо файл існує)
|
||||
self._manifest: dict[str, str] = self._load_manifest()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# SAT-01: Local tile reads (no HTTP)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _load_manifest(self) -> dict[str, str]:
|
||||
"""Завантажити SHA-256 manifest з tile_dir/manifest.sha256."""
|
||||
path = os.path.join(self.tile_dir, "manifest.sha256")
|
||||
if not os.path.isfile(path):
|
||||
return {}
|
||||
manifest: dict[str, str] = {}
|
||||
with open(path) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
parts = line.split(maxsplit=1)
|
||||
if len(parts) == 2:
|
||||
manifest[parts[1].strip()] = parts[0].strip()
|
||||
return manifest
|
||||
|
||||
def _verify_tile_integrity(self, rel_path: str, file_path: str) -> bool:
|
||||
"""Перевірити SHA-256 тайла проти manifest (якщо manifest існує)."""
|
||||
if not self._manifest:
|
||||
return True # без manifest — пропускаємо
|
||||
expected = self._manifest.get(rel_path)
|
||||
if expected is None:
|
||||
return True # тайл не в manifest — OK
|
||||
sha = hashlib.sha256()
|
||||
with open(file_path, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(8192), b""):
|
||||
sha.update(chunk)
|
||||
actual = sha.hexdigest()
|
||||
if actual != expected:
|
||||
self._logger.warning("Tile integrity failed: %s (exp %s, got %s)",
|
||||
rel_path, expected[:12], actual[:12])
|
||||
return False
|
||||
return True
|
||||
|
||||
def load_local_tile(self, tile_coords: TileCoords) -> np.ndarray | None:
|
||||
"""Load a tile image from the local pre-loaded directory.
|
||||
|
||||
@@ -52,11 +92,14 @@ class SatelliteDataManager:
|
||||
if key in self._mem_cache:
|
||||
return self._mem_cache[key]
|
||||
|
||||
path = os.path.join(self.tile_dir, str(tile_coords.zoom),
|
||||
str(tile_coords.x), f"{tile_coords.y}.png")
|
||||
rel_path = f"{tile_coords.zoom}/{tile_coords.x}/{tile_coords.y}.png"
|
||||
path = os.path.join(self.tile_dir, rel_path)
|
||||
if not os.path.isfile(path):
|
||||
return None
|
||||
|
||||
if not self._verify_tile_integrity(rel_path, path):
|
||||
return None # тайл пошкоджений
|
||||
|
||||
img = cv2.imread(path, cv2.IMREAD_COLOR)
|
||||
if img is None:
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user