"""Abstract base class for AYN threat-intelligence feeds.""" from __future__ import annotations import logging import time from abc import ABC, abstractmethod from datetime import datetime from typing import Any, Dict, List, Optional logger = logging.getLogger(__name__) class BaseFeed(ABC): """Common interface for all external threat-intelligence feeds. Provides rate-limiting, last-updated tracking, and a uniform ``fetch()`` contract so the :class:`SignatureManager` can orchestrate updates without knowing feed internals. Parameters ---------- rate_limit_seconds: Minimum interval between successive HTTP requests to the same feed. """ def __init__(self, rate_limit_seconds: float = 2.0) -> None: self._rate_limit = rate_limit_seconds self._last_request_time: float = 0.0 self._last_updated: Optional[datetime] = None # ------------------------------------------------------------------ # Identity # ------------------------------------------------------------------ @abstractmethod def get_name(self) -> str: """Return a short, human-readable feed name.""" ... # ------------------------------------------------------------------ # Fetching # ------------------------------------------------------------------ @abstractmethod def fetch(self) -> List[Dict[str, Any]]: """Download the latest entries from the feed. Returns a list of dicts. The exact keys depend on the feed type (hashes, IOCs, rules, etc.). The :class:`SignatureManager` is responsible for routing each entry to the correct database. """ ... # ------------------------------------------------------------------ # State # ------------------------------------------------------------------ @property def last_updated(self) -> Optional[datetime]: """Timestamp of the most recent successful fetch.""" return self._last_updated def _mark_updated(self) -> None: """Record the current time as the last-successful-fetch timestamp.""" self._last_updated = datetime.utcnow() # ------------------------------------------------------------------ # Rate limiting # ------------------------------------------------------------------ def _rate_limit_wait(self) -> None: """Block until the rate-limit window has elapsed.""" elapsed = time.monotonic() - self._last_request_time remaining = self._rate_limit - elapsed if remaining > 0: logger.debug("[%s] Rate-limiting: sleeping %.1fs", self.get_name(), remaining) time.sleep(remaining) self._last_request_time = time.monotonic() # ------------------------------------------------------------------ # Logging helpers # ------------------------------------------------------------------ def _log(self, msg: str, *args: Any) -> None: logger.info("[%s] " + msg, self.get_name(), *args) def _warn(self, msg: str, *args: Any) -> None: logger.warning("[%s] " + msg, self.get_name(), *args) def _error(self, msg: str, *args: Any) -> None: logger.error("[%s] " + msg, self.get_name(), *args)