remove infra.md.example, infra.md is the source of truth
This commit is contained in:
92
ayn-antivirus/ayn_antivirus/signatures/feeds/base_feed.py
Normal file
92
ayn-antivirus/ayn_antivirus/signatures/feeds/base_feed.py
Normal file
@@ -0,0 +1,92 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user