Files
adaptive-multi-protocol-bro…/ampbrowser/adapters.py
T

169 lines
5.8 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
import os
from pathlib import Path
import shutil
import socket
@dataclass(frozen=True)
class TransportStatus:
transport: str
installed: bool
running: bool
endpoint: str
adoptable: bool
manage_supported: bool
note: str
@dataclass(frozen=True)
class TransportAdapter:
name: str
profile: str
endpoint: str
adopt_check: str
managed_state: str
install_strategy: str
start_strategy: str
stop_policy: str
note: str
commands: tuple[str, ...] = ()
connect_host: str = ""
connect_port: int = 0
builtin_running: bool = False
manage_supported: bool = True
def inspect(self) -> TransportStatus:
installed = self.builtin_running or any(_command_available(command) for command in self.commands)
running = self.builtin_running or (
bool(self.connect_host) and bool(self.connect_port) and _can_connect(self.connect_host, self.connect_port)
)
return TransportStatus(
transport=self.name,
installed=installed,
running=running,
endpoint=self.endpoint,
adoptable=running,
manage_supported=self.manage_supported,
note=self.note,
)
def setup_steps(self, *, installed: bool, platform: str) -> tuple[str, ...]:
steps: list[str] = []
if not installed and self.install_strategy != "unsupported":
steps.append(self.install_strategy)
if platform == "android":
steps.append(f"start visible Android foreground service for {self.name}")
steps.append(f"start managed Android {self.name} transport")
steps.append(f"wait for {self.endpoint}")
return tuple(steps)
elif platform == "ios":
if self.name == "tor":
steps.append("start foreground-only iOS Arti client session")
steps.append("attach in-app onion networking bridge")
steps.append("wait for in-app Tor readiness")
return tuple(steps)
steps.append(f"start foreground-only iOS session for {self.name}")
steps.append(f"start foreground-only iOS {self.name} transport")
steps.append(f"wait for {self.endpoint}")
return tuple(steps)
if self.start_strategy != "unsupported":
steps.append(self.start_strategy)
steps.append(f"wait for {self.endpoint}")
return tuple(steps)
ADAPTERS: dict[str, TransportAdapter] = {
"tor": TransportAdapter(
name="tor",
profile="tor",
endpoint="socks5://127.0.0.1:9050",
adopt_check="SOCKS on 127.0.0.1:9050",
managed_state=".ampb/transports/tor",
install_strategy="install or bundle Arti/Tor provider",
start_strategy="start managed Arti SOCKS proxy",
stop_policy="stop only AMPB-owned Tor daemon",
note="Tor via existing SOCKS proxy or managed Arti/Tor runtime",
commands=("ampb-bundled-arti", "arti", "tor"),
connect_host="127.0.0.1",
connect_port=9050,
),
"i2p": TransportAdapter(
name="i2p",
profile="i2p",
endpoint="http://127.0.0.1:4444",
adopt_check="HTTP proxy on 127.0.0.1:4444",
managed_state=".ampb/transports/i2p",
install_strategy="install I2P provider",
start_strategy="start managed I2P router",
stop_policy="stop only AMPB-owned I2P router",
note="I2P HTTP proxy",
commands=("i2pd", "i2prouter"),
connect_host="127.0.0.1",
connect_port=4444,
),
"reticulum": TransportAdapter(
name="reticulum",
profile="reticulum",
endpoint="rns://local",
adopt_check="RNS tools and configured interfaces",
managed_state=".ampb/transports/reticulum",
install_strategy="install Reticulum provider",
start_strategy="start configured Reticulum interface",
stop_policy="stop only AMPB-owned Reticulum process",
note="Reticulum adapter is planned for resilient/private routing; it is not an anonymity layer",
commands=("rnsd", "rnstatus", "rnodeconf"),
manage_supported=False,
),
"ipfs": TransportAdapter(
name="ipfs",
profile="ipfs",
endpoint="http://127.0.0.1:8080",
adopt_check="HTTP gateway on 127.0.0.1:8080",
managed_state=".ampb/transports/ipfs",
install_strategy="install IPFS/Kubo provider",
start_strategy="start managed IPFS gateway",
stop_policy="stop only AMPB-owned IPFS process",
note="IPFS local gateway for content-addressed distribution; not an anonymity layer",
commands=("ipfs",),
connect_host="127.0.0.1",
connect_port=8080,
),
"gemini": TransportAdapter(
name="gemini",
profile="gemini",
endpoint="builtin://gemtext-renderer",
adopt_check="built-in renderer available",
managed_state=".ampb/transports/gemini",
install_strategy="unsupported",
start_strategy="use built-in Gemtext fetch/render path",
stop_policy="no daemon to stop",
note="Built-in Gemtext fetch/render path",
builtin_running=True,
),
}
def adapter_for(name: str) -> TransportAdapter | None:
return ADAPTERS.get(name)
def _command_available(command: str) -> bool:
if command == "ampb-bundled-arti":
path = Path(os.environ.get("AMPB_BROWSER_BUILD_ROOT", "/tmp/ampb-browser-build")) / "providers/arti/bin/arti"
return path.exists() and path.is_file()
if "/" in command:
path = Path(command)
return path.exists() and path.is_file()
return bool(shutil.which(command))
def _can_connect(host: str, port: int) -> bool:
try:
with socket.create_connection((host, port), timeout=0.2):
return True
except OSError:
return False