diff --git a/hippolyzer/lib/base/network/caps_client.py b/hippolyzer/lib/base/network/caps_client.py index 146420c..1868bf4 100644 --- a/hippolyzer/lib/base/network/caps_client.py +++ b/hippolyzer/lib/base/network/caps_client.py @@ -82,8 +82,9 @@ CAPS_DICT = Union[ class CapsClient: - def __init__(self, caps: Optional[CAPS_DICT] = None): + def __init__(self, caps: Optional[CAPS_DICT] = None, session: Optional[aiohttp.ClientSession] = None) -> None: self._caps = caps + self._session = session def _request_fixups(self, cap_or_url: str, headers: Dict, proxy: Optional[bool], ssl: Any): return cap_or_url, headers, proxy, ssl @@ -117,6 +118,7 @@ class CapsClient: session_owned = False # Use an existing session if we have one to take advantage of connection pooling # otherwise create one + session = session or self._session if session is None: session_owned = True session = aiohttp.ClientSession( diff --git a/hippolyzer/lib/client/hippo_client.py b/hippolyzer/lib/client/hippo_client.py new file mode 100644 index 0000000..901ec3c --- /dev/null +++ b/hippolyzer/lib/client/hippo_client.py @@ -0,0 +1,273 @@ +from __future__ import annotations + +import hashlib +from importlib.metadata import version +import logging +import uuid +import weakref +import xmlrpc.client +from typing import * + +import aiohttp +import multidict + +from hippolyzer.lib.base.helpers import proxify +from hippolyzer.lib.base.message.circuit import Circuit +from hippolyzer.lib.base.message.message_handler import MessageHandler +from hippolyzer.lib.base.network.caps_client import CapsClient +from hippolyzer.lib.base.network.transport import ADDR_TUPLE +from hippolyzer.lib.base.transfer_manager import TransferManager +from hippolyzer.lib.base.xfer_manager import XferManager +from hippolyzer.lib.client.asset_uploader import AssetUploader +from hippolyzer.lib.client.object_manager import ClientObjectManager +from hippolyzer.lib.client.state import BaseClientSession, BaseClientRegion, BaseClientSessionManager + + +class HippoCapsClient(CapsClient): + def _request_fixups(self, cap_or_url: str, headers: Dict, proxy: Optional[bool], ssl: Any): + headers["User-Agent"] = f"Hippolyzer/v{version('hippolyzer')}" + + +class HippoClientRegion(BaseClientRegion): + def __init__(self, circuit_addr, seed_cap: str, session: HippoClientSession, handle=None): + super().__init__() + self.caps = multidict.MultiDict() + self.objects = ClientObjectManager(proxify(self)) + self.message_handler = MessageHandler() + self.circuit_addr = circuit_addr + self.handle = handle + if seed_cap: + self.caps["Seed"] = seed_cap + self.session: Callable[[], HippoClientSession] = weakref.ref(session) + self.caps_client = HippoCapsClient(self.caps, session.http_session) + self.xfer_manager = XferManager(proxify(self), self.session().secure_session_id) + self.transfer_manager = TransferManager(proxify(self), session.agent_id, session.id) + self.asset_uploader = AssetUploader(proxify(self)) + + def update_caps(self, caps: Mapping[str, str]) -> None: + self.caps.update(caps) + + @property + def cap_urls(self) -> multidict.MultiDict: + return self.caps.copy() + + +class HippoClientSession(BaseClientSession): + """Represents a client's view of a remote session""" + REGION_CLS = HippoClientRegion + + region_by_handle: Callable[[int], Optional[HippoClientRegion]] + region_by_circuit_addr: Callable[[ADDR_TUPLE], Optional[HippoClientRegion]] + + def __init__(self, id, secure_session_id, agent_id, circuit_code, client: Optional[HippoClient] = None, + login_data=None): + super().__init__(id, secure_session_id, agent_id, circuit_code, client, login_data=login_data) + self.http_session = client.http_session + + def register_region(self, circuit_addr: Optional[ADDR_TUPLE] = None, seed_url: Optional[str] = None, + handle: Optional[int] = None) -> HippoClientRegion: + return super().register_region(circuit_addr, seed_url, handle) # type:ignore + + def open_circuit(self, near_addr, circuit_addr, transport): + for region in self.regions: + if region.circuit_addr == circuit_addr: + if not region.circuit or not region.circuit.is_alive: + region.circuit = Circuit(near_addr, circuit_addr, transport) + return True + if region.circuit and region.circuit.is_alive: + # Whatever, already open + logging.debug("Tried to re-open circuit for %r" % (circuit_addr,)) + return True + return False + + +class HippoClient(BaseClientSessionManager): + SUPPORTED_CAPS: Set[str] = { + "AbuseCategories", + "AcceptFriendship", + "AcceptGroupInvite", + "AgentPreferences", + "AgentProfile", + "AgentState", + "AttachmentResources", + "AvatarPickerSearch", + "AvatarRenderInfo", + "CharacterProperties", + "ChatSessionRequest", + "CopyInventoryFromNotecard", + "CreateInventoryCategory", + "DeclineFriendship", + "DeclineGroupInvite", + "DispatchRegionInfo", + "DirectDelivery", + "EnvironmentSettings", + "EstateAccess", + "DispatchOpenRegionSettings", + "EstateChangeInfo", + "EventQueueGet", + "ExtEnvironment", + "FetchLib2", + "FetchLibDescendents2", + "FetchInventory2", + "FetchInventoryDescendents2", + "IncrementCOFVersion", + "InventoryAPIv3", + "LibraryAPIv3", + "InterestList", + "InventoryThumbnailUpload", + "GetDisplayNames", + "GetExperiences", + "AgentExperiences", + "FindExperienceByName", + "GetExperienceInfo", + "GetAdminExperiences", + "GetCreatorExperiences", + "ExperiencePreferences", + "GroupExperiences", + "UpdateExperience", + "IsExperienceAdmin", + "IsExperienceContributor", + "RegionExperiences", + "ExperienceQuery", + "GetMesh", + "GetMesh2", + "GetMetadata", + "GetObjectCost", + "GetObjectPhysicsData", + "GetTexture", + "GroupAPIv1", + "GroupMemberData", + "GroupProposalBallot", + "HomeLocation", + "LandResources", + "LSLSyntax", + "MapLayer", + "MapLayerGod", + "MeshUploadFlag", + "NavMeshGenerationStatus", + "NewFileAgentInventory", + "ObjectAnimation", + "ObjectMedia", + "ObjectMediaNavigate", + "ObjectNavMeshProperties", + "ParcelPropertiesUpdate", + "ParcelVoiceInfoRequest", + "ProductInfoRequest", + "ProvisionVoiceAccountRequest", + "ReadOfflineMsgs", + "RegionObjects", + "RemoteParcelRequest", + "RenderMaterials", + "RequestTextureDownload", + "ResourceCostSelected", + "RetrieveNavMeshSrc", + "SearchStatRequest", + "SearchStatTracking", + "SendPostcard", + "SendUserReport", + "SendUserReportWithScreenshot", + "ServerReleaseNotes", + "SetDisplayName", + "SimConsoleAsync", + "SimulatorFeatures", + "StartGroupProposal", + "TerrainNavMeshProperties", + "TextureStats", + "UntrustedSimulatorMessage", + "UpdateAgentInformation", + "UpdateAgentLanguage", + "UpdateAvatarAppearance", + "UpdateGestureAgentInventory", + "UpdateGestureTaskInventory", + "UpdateNotecardAgentInventory", + "UpdateNotecardTaskInventory", + "UpdateScriptAgent", + "UpdateScriptTask", + "UpdateSettingsAgentInventory", + "UpdateSettingsTaskInventory", + "UploadAgentProfileImage", + "UploadBakedTexture", + "UserInfo", + "ViewerAsset", + "ViewerBenefits", + "ViewerMetrics", + "ViewerStartAuction", + "ViewerStats", + } + + DEFAULT_OPTIONS = { + "inventory-root", + "inventory-skeleton", + "inventory-lib-root", + "inventory-lib-owner", + "inventory-skel-lib", + "initial-outfit", + "gestures", + "display_names", + "event_notifications", + "classified_categories", + "adult_compliant", + "buddy-list", + "newuser-config", + "ui-config", + "advanced-mode", + "max-agent-groups", + "map-server-url", + "voice-config", + "tutorial_setting", + "login-flags", + "global-textures", + # Not an official option, just so this can be tracked. + "pyogp-client", + } + + DEFAULT_LOGIN_URI = "https://login.agni.lindenlab.com/cgi-bin/login.cgi" + + def __init__(self, options: Optional[Set[str]] = None): + self._username: Optional[str] = None + self._password: Optional[str] = None + self._mac = uuid.getnode() + self._options = options if options is not None else self.DEFAULT_OPTIONS + self.http_session = aiohttp.ClientSession() + + async def aclose(self): + await self.http_session.close() + + async def login(self, username: str, password: str, login_uri: Optional[str] = "", agree_to_tos: bool = False): + if not login_uri: + login_uri = self.DEFAULT_LOGIN_URI + + split_username = username.split(" ") + if len(split_username) < 2: + first_name = split_username[0] + last_name = "Resident" + else: + first_name, last_name = split_username + + payload = { + "address_size": 64, + "agree_to_tos": int(agree_to_tos), + "channel": "Hippolyzer", + "extended_errors": 1, + "first": first_name, + "last": last_name, + "host_id": "", + "id0": hashlib.md5(str(self._mac).encode("ascii")).hexdigest(), + "mac": hashlib.md5(str(self._mac).encode("ascii")).hexdigest(), + "mfa_hash": "", + "passwd": "$1$" + hashlib.md5(str(password).encode("ascii")).hexdigest(), + # TODO: actually get these + "platform": "lnx", + "platform_string": "Linux 6.6", + # TODO: What is this? + "platform_version": "2.38.0", + "read_critical": 0, + "start": "home", + "token": "", + "version": version("hippolyzer"), + "options": list(self._options), + } + rpc_payload = xmlrpc.client.dumps((payload,), "login_to_simulator") + async with self.http_session.post(login_uri, data=rpc_payload, headers={"Content-Type": "text/xml"}) as resp: + resp.raise_for_status() + print(await resp.read()) diff --git a/hippolyzer/lib/client/inventory_manager.py b/hippolyzer/lib/client/inventory_manager.py index 3a6473a..7fb7577 100644 --- a/hippolyzer/lib/client/inventory_manager.py +++ b/hippolyzer/lib/client/inventory_manager.py @@ -34,7 +34,7 @@ class InventoryManager: # completion from the inventory cache. This matches indra's behavior. version=InventoryCategory.VERSION_NONE, type="category", - pref_type=skel_cat.get("type_default", -1), + pref_type=skel_cat.get("type_default", "-1"), owner_id=self._session.agent_id, )) diff --git a/hippolyzer/lib/client/object_manager.py b/hippolyzer/lib/client/object_manager.py index 0c05c74..f120813 100644 --- a/hippolyzer/lib/client/object_manager.py +++ b/hippolyzer/lib/client/object_manager.py @@ -27,9 +27,11 @@ from hippolyzer.lib.base.objects import ( ) from hippolyzer.lib.base.settings import Settings from hippolyzer.lib.client.namecache import NameCache, NameCacheEntry -from hippolyzer.lib.client.state import BaseClientSession, BaseClientRegion from hippolyzer.lib.base.templates import PCode, ObjectStateSerializer +if TYPE_CHECKING: + from hippolyzer.lib.client.state import BaseClientRegion, BaseClientSession + LOG = logging.getLogger(__name__) OBJECT_OR_LOCAL = Union[Object, int] diff --git a/hippolyzer/lib/client/state.py b/hippolyzer/lib/client/state.py index e250acf..803e7eb 100644 --- a/hippolyzer/lib/client/state.py +++ b/hippolyzer/lib/client/state.py @@ -4,17 +4,21 @@ Base classes for common session-related state shared between clients and proxies from __future__ import annotations import abc +import logging +import weakref from typing import * -from hippolyzer.lib.base.datatypes import UUID -from hippolyzer.lib.base.message.circuit import ConnectionHolder +import multidict + +from hippolyzer.lib.base.datatypes import UUID, Vector3 +from hippolyzer.lib.base.message.circuit import ConnectionHolder, Circuit from hippolyzer.lib.base.message.message import Message from hippolyzer.lib.base.message.message_handler import MessageHandler from hippolyzer.lib.base.network.caps_client import CapsClient from hippolyzer.lib.base.network.transport import ADDR_TUPLE +from hippolyzer.lib.base.objects import handle_to_global_pos -if TYPE_CHECKING: - from hippolyzer.lib.client.object_manager import ClientObjectManager, ClientWorldObjectManager +from hippolyzer.lib.client.object_manager import ClientObjectManager, ClientWorldObjectManager class BaseClientRegion(ConnectionHolder, abc.ABC): @@ -24,6 +28,53 @@ class BaseClientRegion(ConnectionHolder, abc.ABC): session: Callable[[], BaseClientSession] objects: ClientObjectManager caps_client: CapsClient + cap_urls: multidict.MultiDict[str] + circuit_addr: ADDR_TUPLE + circuit: Optional[Circuit] + _name: Optional[str] + + def __init__(self): + self._name = None + self.circuit = None + + @abc.abstractmethod + def update_caps(self, caps: Mapping[str, str]) -> None: + pass + + @property + def name(self): + if self._name: + return self._name + return "Pending %r" % (self.circuit_addr,) + + @name.setter + def name(self, val): + self._name = val + + @property + def global_pos(self) -> Vector3: + if self.handle is None: + raise ValueError("Can't determine global region position without handle") + return handle_to_global_pos(self.handle) + + @property + def is_alive(self): + if not self.circuit: + return False + return self.circuit.is_alive + + def mark_dead(self): + logging.info("Marking %r dead" % self) + if self.circuit: + self.circuit.is_alive = False + self.objects.clear() + + def __repr__(self): + return "<%s %s>" % (self.__class__.__name__, self.name) + + +class BaseClientSessionManager(abc.ABC): + pass class BaseClientSession(abc.ABC): @@ -32,8 +83,96 @@ class BaseClientSession(abc.ABC): agent_id: UUID secure_session_id: UUID message_handler: MessageHandler[Message, str] - regions: Sequence[BaseClientRegion] + regions: MutableSequence[BaseClientRegion] region_by_handle: Callable[[int], Optional[BaseClientRegion]] region_by_circuit_addr: Callable[[ADDR_TUPLE], Optional[BaseClientRegion]] objects: ClientWorldObjectManager login_data: Dict[str, Any] + REGION_CLS = Type[BaseClientRegion] + + def __init__(self, id, secure_session_id, agent_id, circuit_code, + session_manager: Optional[BaseClientSessionManager], login_data=None): + self.login_data = login_data or {} + self.pending = True + self.id: UUID = id + self.secure_session_id: UUID = secure_session_id + self.agent_id: UUID = agent_id + self.circuit_code = circuit_code + self.global_caps = {} + self.session_manager = session_manager + self.regions = [] + self._main_region = None + self.message_handler: MessageHandler[Message, str] = MessageHandler() + super().__init__() + + @classmethod + def from_login_data(cls, login_data, session_manager): + sess = cls( + id=UUID(login_data["session_id"]), + secure_session_id=UUID(login_data["secure_session_id"]), + agent_id=UUID(login_data["agent_id"]), + circuit_code=int(login_data["circuit_code"]), + session_manager=session_manager, + login_data=login_data, + ) + appearance_service = login_data.get("agent_appearance_service") + map_image_service = login_data.get("map-server-url") + if appearance_service: + sess.global_caps["AppearanceService"] = appearance_service + if map_image_service: + sess.global_caps["MapImageService"] = map_image_service + # Login data also has details about the initial sim + sess.register_region( + circuit_addr=(login_data["sim_ip"], login_data["sim_port"]), + handle=(login_data["region_x"] << 32) | login_data["region_y"], + seed_url=login_data["seed_capability"], + ) + return sess + + def register_region(self, circuit_addr: Optional[ADDR_TUPLE] = None, seed_url: Optional[str] = None, + handle: Optional[int] = None) -> BaseClientRegion: + if not any((circuit_addr, seed_url)): + raise ValueError("One of circuit_addr and seed_url must be defined!") + + for region in self.regions: + if region.circuit_addr == circuit_addr: + if seed_url and region.cap_urls.get("Seed") != seed_url: + region.update_caps({"Seed": seed_url}) + if handle: + region.handle = handle + return region + if seed_url and region.cap_urls.get("Seed") == seed_url: + return region + + if not circuit_addr: + raise ValueError("Can't create region without circuit addr!") + + logging.info("Registering region for %r" % (circuit_addr,)) + region = self.REGION_CLS(circuit_addr, seed_url, self, handle=handle) + self.regions.append(region) + return region + + @property + def main_region(self) -> Optional[BaseClientRegion]: + if self._main_region and self._main_region() in self.regions: + return self._main_region() + return None + + @main_region.setter + def main_region(self, val: BaseClientRegion): + self._main_region = weakref.ref(val) + + def transaction_to_assetid(self, transaction_id: UUID): + return UUID.combine(transaction_id, self.secure_session_id) + + def region_by_circuit_addr(self, circuit_addr) -> Optional[BaseClientRegion]: + for region in self.regions: + if region.circuit_addr == circuit_addr and region.circuit: + return region + return None + + def region_by_handle(self, handle: int) -> Optional[BaseClientRegion]: + for region in self.regions: + if region.handle == handle: + return region + return None diff --git a/hippolyzer/lib/proxy/region.py b/hippolyzer/lib/proxy/region.py index 372f320..85e9458 100644 --- a/hippolyzer/lib/proxy/region.py +++ b/hippolyzer/lib/proxy/region.py @@ -1,6 +1,5 @@ from __future__ import annotations -import logging import hashlib import uuid import weakref @@ -9,12 +8,11 @@ import urllib.parse import multidict -from hippolyzer.lib.base.datatypes import Vector3, UUID +from hippolyzer.lib.base.datatypes import UUID from hippolyzer.lib.base.helpers import proxify from hippolyzer.lib.base.message.llsd_msg_serializer import LLSDMessageSerializer from hippolyzer.lib.base.message.message import Message, Block from hippolyzer.lib.base.message.message_handler import MessageHandler -from hippolyzer.lib.base.objects import handle_to_global_pos from hippolyzer.lib.client.state import BaseClientRegion from hippolyzer.lib.proxy.caps_client import ProxyCapsClient from hippolyzer.lib.proxy.circuit import ProxiedCircuit @@ -44,14 +42,15 @@ class CapsMultiDict(multidict.MultiDict[Tuple[CapType, str]]): class ProxiedRegion(BaseClientRegion): + circuit: Optional[ProxiedCircuit] + def __init__(self, circuit_addr, seed_cap: str, session: Session, handle=None): + super().__init__() # A client may make a Seed request twice, and may get back two (valid!) sets of # Cap URIs. We need to be able to look up both, so MultiDict is necessary. self.handle: Optional[int] = handle - self._name: Optional[str] = None # TODO: when does this change? self.cache_id: Optional[UUID] = None - self.circuit: Optional[ProxiedCircuit] = None self.circuit_addr = circuit_addr self.caps = CapsMultiDict() # Reverse lookup for URL -> cap data @@ -71,31 +70,9 @@ class ProxiedRegion(BaseClientRegion): self._recalc_caps() @property - def name(self): - if self._name: - return self._name - return "Pending %r" % (self.circuit_addr,) - - @name.setter - def name(self, val): - self._name = val - - @property - def cap_urls(self) -> multidict.MultiDict[str, str]: + def cap_urls(self) -> multidict.MultiDict[str]: return multidict.MultiDict((x, y[1]) for x, y in self.caps.items()) - @property - def global_pos(self) -> Vector3: - if self.handle is None: - raise ValueError("Can't determine global region position without handle") - return handle_to_global_pos(self.handle) - - @property - def is_alive(self): - if not self.circuit: - return False - return self.circuit.is_alive - def update_caps(self, caps: Mapping[str, str]): for cap_name, cap_url in caps.items(): if isinstance(cap_url, str) and cap_url.startswith('http'): @@ -158,15 +135,9 @@ class ProxiedRegion(BaseClientRegion): return None def mark_dead(self): - logging.info("Marking %r dead" % self) - if self.circuit: - self.circuit.is_alive = False - self.objects.clear() + super().mark_dead() self.eq_manager.clear() - def __repr__(self): - return "<%s %s>" % (self.__class__.__name__, self.name) - class EventQueueManager: def __init__(self, region: ProxiedRegion): diff --git a/hippolyzer/lib/proxy/sessions.py b/hippolyzer/lib/proxy/sessions.py index 65ab025..b92e62c 100644 --- a/hippolyzer/lib/proxy/sessions.py +++ b/hippolyzer/lib/proxy/sessions.py @@ -6,7 +6,6 @@ import datetime import functools import logging import multiprocessing -import weakref from typing import * from weakref import ref @@ -14,9 +13,9 @@ from outleap import LEAPClient from hippolyzer.lib.base.datatypes import UUID from hippolyzer.lib.base.helpers import proxify -from hippolyzer.lib.base.message.message import Message from hippolyzer.lib.base.message.message_handler import MessageHandler -from hippolyzer.lib.client.state import BaseClientSession +from hippolyzer.lib.base.network.transport import ADDR_TUPLE +from hippolyzer.lib.client.state import BaseClientSession, BaseClientSessionManager from hippolyzer.lib.proxy.addons import AddonManager from hippolyzer.lib.proxy.circuit import ProxiedCircuit from hippolyzer.lib.proxy.http_asset_repo import HTTPAssetRepo @@ -34,30 +33,34 @@ if TYPE_CHECKING: class Session(BaseClientSession): - def __init__(self, session_id, secure_session_id, agent_id, circuit_code, + regions: MutableSequence[ProxiedRegion] + region_by_handle: Callable[[int], Optional[ProxiedRegion]] + region_by_circuit_addr: Callable[[ADDR_TUPLE], Optional[ProxiedRegion]] + main_region: Optional[ProxiedRegion] + REGION_CLS = ProxiedRegion + + def __init__(self, id, secure_session_id, agent_id, circuit_code, session_manager: Optional[SessionManager], login_data=None): - self.login_data = login_data or {} - self.pending = True - self.id: UUID = session_id - self.secure_session_id: UUID = secure_session_id - self.agent_id: UUID = agent_id - self.circuit_code = circuit_code - self.global_caps = {} + super().__init__( + id=id, + secure_session_id=secure_session_id, + agent_id=agent_id, + circuit_code=circuit_code, + session_manager=session_manager, + login_data=login_data, + ) # Bag of arbitrary data addons can use to persist data across addon reloads # Each addon name gets its own separate dict within this dict self.addon_ctx: Dict[str, Dict[str, Any]] = collections.defaultdict(dict) - self.session_manager: SessionManager = session_manager or None + self.session_manager: SessionManager = session_manager self.selected: SelectionModel = SelectionModel() - self.regions: List[ProxiedRegion] = [] self.started_at = datetime.datetime.now() - self.message_handler: MessageHandler[Message, str] = MessageHandler() self.http_message_handler: MessageHandler[HippoHTTPFlow, str] = MessageHandler() self.objects = ProxyWorldObjectManager(self, session_manager.settings, session_manager.name_cache) self.inventory = ProxyInventoryManager(proxify(self)) self.leap_client: Optional[LEAPClient] = None # Base path of a newview type cache directory for this session self.cache_dir: Optional[str] = None - self._main_region = None @property def global_addon_ctx(self): @@ -65,77 +68,13 @@ class Session(BaseClientSession): return {} return self.session_manager.addon_ctx - @classmethod - def from_login_data(cls, login_data, session_manager): - sess = Session( - session_id=UUID(login_data["session_id"]), - secure_session_id=UUID(login_data["secure_session_id"]), - agent_id=UUID(login_data["agent_id"]), - circuit_code=int(login_data["circuit_code"]), - session_manager=session_manager, - login_data=login_data, - ) - appearance_service = login_data.get("agent_appearance_service") - map_image_service = login_data.get("map-server-url") - if appearance_service: - sess.global_caps["AppearanceService"] = appearance_service - if map_image_service: - sess.global_caps["MapImageService"] = map_image_service - # Login data also has details about the initial sim - sess.register_region( - circuit_addr=(login_data["sim_ip"], login_data["sim_port"]), - handle=(login_data["region_x"] << 32) | login_data["region_y"], - seed_url=login_data["seed_capability"], - ) - return sess - - @property - def main_region(self) -> Optional[ProxiedRegion]: - if self._main_region and self._main_region() in self.regions: - return self._main_region() - return None - - @main_region.setter - def main_region(self, val: ProxiedRegion): - self._main_region = weakref.ref(val) - - def register_region(self, circuit_addr: Optional[Tuple[str, int]] = None, + def register_region(self, circuit_addr: Optional[ADDR_TUPLE] = None, seed_url: Optional[str] = None, handle: Optional[int] = None) -> ProxiedRegion: - if not any((circuit_addr, seed_url)): - raise ValueError("One of circuit_addr and seed_url must be defined!") - - for region in self.regions: - if region.circuit_addr == circuit_addr: - if seed_url and region.cap_urls.get("Seed") != seed_url: - region.update_caps({"Seed": seed_url}) - if handle: - region.handle = handle - return region - if seed_url and region.cap_urls.get("Seed") == seed_url: - return region - - if not circuit_addr: - raise ValueError("Can't create region without circuit addr!") - - logging.info("Registering region for %r" % (circuit_addr,)) - region = ProxiedRegion(circuit_addr, seed_url, self, handle=handle) - self.regions.append(region) + region: ProxiedRegion = super().register_region(circuit_addr, seed_url, handle) # type: ignore AddonManager.handle_region_registered(self, region) return region - def region_by_circuit_addr(self, circuit_addr) -> Optional[ProxiedRegion]: - for region in self.regions: - if region.circuit_addr == circuit_addr and region.circuit: - return region - return None - - def region_by_handle(self, handle: int) -> Optional[ProxiedRegion]: - for region in self.regions: - if region.handle == handle: - return region - return None - def open_circuit(self, near_addr, circuit_addr, transport): for region in self.regions: if region.circuit_addr == circuit_addr: @@ -175,15 +114,13 @@ class Session(BaseClientSession): return CapData(cap_name, ref(region), ref(self), base_url, cap_type) return None - def transaction_to_assetid(self, transaction_id: UUID): - return UUID.combine(transaction_id, self.secure_session_id) - def __repr__(self): return "<%s %s>" % (self.__class__.__name__, self.id) -class SessionManager: +class SessionManager(BaseClientSessionManager): def __init__(self, settings: ProxySettings): + super().__init__() self.settings: ProxySettings = settings self.sessions: List[Session] = [] self.shutdown_signal = multiprocessing.Event()