Move Circuit and Message to lib.base

Fairly invasive, but will help make lib.base useful again. No
more Message / ProxiedMessage split!
This commit is contained in:
Salad Dais
2021-06-03 02:58:41 +00:00
parent 908d7a24f1
commit a39d025a04
51 changed files with 761 additions and 725 deletions

View File

@@ -5,7 +5,7 @@ Except for backward, which makes you go left.
"""
from hippolyzer.lib.base.templates import AgentControlFlags
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.proxy.addon_utils import BaseAddon
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
@@ -19,7 +19,7 @@ BACK_MASK = (AgentControlFlags.AT_NEG | AgentControlFlags.NUDGE_AT_NEG)
class BackwardsAddon(BaseAddon):
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: ProxiedMessage):
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: Message):
if message.name == "AgentUpdate":
agent_data_block = message["AgentData"][0]
flags: AgentControlFlags = agent_data_block.deserialize_var("ControlFlags")

View File

@@ -11,7 +11,7 @@ import secrets
from hippolyzer.lib.base.datatypes import UUID
from hippolyzer.lib.proxy.addon_utils import BaseAddon, SessionProperty
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
@@ -41,7 +41,7 @@ class BezosifyAddon(BaseAddon):
# random value to XOR all CRCs with
self.bezos_crc_xor = secrets.randbits(32)
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: ProxiedMessage):
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: Message):
if message.name == "ObjectUpdateCached":
for block in message["ObjectData"]:
# Cached only really has a CRC, this will force the cache miss.

View File

@@ -14,15 +14,14 @@ from typing import *
from PySide2 import QtCore, QtGui, QtWidgets
from hippolyzer.lib.base.datatypes import Vector3
from hippolyzer.lib.base.message.message import Block
from hippolyzer.lib.base.message.message import Block, Message
from hippolyzer.lib.base.objects import Object
from hippolyzer.lib.base.ui_helpers import loadUi
from hippolyzer.lib.base.templates import PCode
from hippolyzer.lib.proxy.addons import AddonManager
from hippolyzer.lib.proxy.addon_utils import BaseAddon, SessionProperty
from hippolyzer.lib.proxy.commands import handle_command
from hippolyzer.lib.proxy.packets import Direction
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.base.network.transport import Direction
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
from hippolyzer.lib.proxy.task_scheduler import TaskLifeScope
@@ -81,7 +80,7 @@ class BlueishObjectListGUIAddon(BaseAddon):
raise
def _highlight_object(self, session: Session, obj: Object):
session.main_region.circuit.send_message(ProxiedMessage(
session.main_region.circuit.send_message(Message(
"ForceObjectSelect",
Block("Header", ResetList=False),
Block("Data", LocalID=obj.LocalID),
@@ -89,7 +88,7 @@ class BlueishObjectListGUIAddon(BaseAddon):
))
def _teleport_to_object(self, session: Session, obj: Object):
session.main_region.circuit.send_message(ProxiedMessage(
session.main_region.circuit.send_message(Message(
"TeleportLocationRequest",
Block("AgentData", AgentID=session.agent_id, SessionID=session.id),
Block(

View File

@@ -1,9 +1,9 @@
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
def handle_lludp_message(session: Session, region: ProxiedRegion, message: ProxiedMessage):
def handle_lludp_message(session: Session, region: ProxiedRegion, message: Message):
# addon_ctx will persist across addon reloads, use for storing data that
# needs to survive across calls to this function
ctx = session.addon_ctx

View File

@@ -10,13 +10,13 @@ message with a greeting.
"""
from hippolyzer.lib.proxy.addon_utils import BaseAddon
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
class CustomMetaExampleAddon(BaseAddon):
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: ProxiedMessage):
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: Message):
if not message.name.startswith("ChatFrom"):
return

View File

@@ -16,8 +16,8 @@ import random
from hippolyzer.lib.base.message.msgtypes import PacketLayout
from hippolyzer.lib.base.message.udpserializer import UDPMessageSerializer
from hippolyzer.lib.proxy.addon_utils import BaseAddon
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.proxy.packets import Direction
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.base.network.transport import Direction
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
@@ -28,7 +28,7 @@ class PacketMutationAddon(BaseAddon):
def __init__(self):
self.serializer = UDPMessageSerializer()
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: ProxiedMessage):
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: Message):
# Only inbound messages, don't fiddle with the sim.
if message.direction != Direction.IN:
return

View File

@@ -3,8 +3,8 @@ Drop outgoing packets that might leak what you're looking at, similar to Firesto
"""
from hippolyzer.lib.base.templates import ViewerEffectType
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.proxy.packets import Direction
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.base.network.transport import Direction
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
@@ -17,7 +17,7 @@ BLOCKED_EFFECTS = (
)
def handle_lludp_message(_session: Session, region: ProxiedRegion, msg: ProxiedMessage):
def handle_lludp_message(_session: Session, region: ProxiedRegion, msg: Message):
if msg.name == "ViewerEffect" and msg.direction == Direction.OUT:
new_blocks = [b for b in msg["Effect"] if b["Type"] not in BLOCKED_EFFECTS]
if new_blocks:

View File

@@ -13,7 +13,7 @@ from hippolyzer.lib.base.datatypes import UUID
from hippolyzer.lib.base.llanim import Animation
from hippolyzer.lib.proxy.addon_utils import AssetAliasTracker, BaseAddon, GlobalProperty
from hippolyzer.lib.proxy.http_flow import HippoHTTPFlow
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session, SessionManager
from hippolyzer.lib.base.vfs import STATIC_VFS
@@ -53,7 +53,7 @@ class HorrorAnimatorAddon(BaseAddon):
# We've reloaded, so make sure assets get new aliases
self.horror_anim_tracker.invalidate_aliases()
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: ProxiedMessage):
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: Message):
tracker = self.horror_anim_tracker
if message.name == "AvatarAnimation":

View File

@@ -16,12 +16,11 @@ import pathlib
from typing import *
from hippolyzer.lib.base.datatypes import UUID
from hippolyzer.lib.base.message.message import Block
from hippolyzer.lib.base.message.message import Block, Message
from hippolyzer.lib.proxy.addons import AddonManager
from hippolyzer.lib.proxy.addon_utils import BaseAddon, SessionProperty
from hippolyzer.lib.proxy.commands import handle_command
from hippolyzer.lib.proxy.http_asset_repo import HTTPAssetRepo
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
@@ -101,7 +100,7 @@ class LocalAnimAddon(BaseAddon):
anim_name: str, new_data: Optional[bytes] = None):
asset_repo: HTTPAssetRepo = session.session_manager.asset_repo
next_id: Optional[UUID] = None
new_msg = ProxiedMessage(
new_msg = Message(
"AgentAnimation",
Block(
"AgentData",

View File

@@ -36,7 +36,7 @@ from hippolyzer.lib.proxy.addon_utils import show_message, BaseAddon, GlobalProp
from hippolyzer.lib.proxy.commands import handle_command
from hippolyzer.lib.proxy.http_asset_repo import HTTPAssetRepo
from hippolyzer.lib.proxy.http_flow import HippoHTTPFlow
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session, SessionManager
@@ -125,7 +125,7 @@ class MeshUploadInterceptingAddon(BaseAddon):
region.objects.request_objects(old_locals)
show_message(f"Cleared target {old_locals}")
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: ProxiedMessage):
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: Message):
# Replace any mesh asset IDs in tracked objects with our local assets
if not self.local_mesh_target_locals:
return

View File

@@ -30,7 +30,7 @@ from hippolyzer.lib.base.multiprocessing_utils import ParentProcessWatcher
from hippolyzer.lib.base.templates import TextureEntry
from hippolyzer.lib.proxy.addon_utils import AssetAliasTracker, BaseAddon, GlobalProperty, AddonProcess
from hippolyzer.lib.proxy.http_flow import HippoHTTPFlow
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session, SessionManager
@@ -98,7 +98,7 @@ class MonochromeAddon(BaseAddon):
# Tell queue consumers to shut down
self.mono_addon_shutdown_signal.set()
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: ProxiedMessage):
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: Message):
tracker = self.mono_tracker
if message.name == "ObjectUpdateCached":
for block in message["ObjectData"]:

View File

@@ -3,16 +3,15 @@ Do the money dance whenever someone in the sim pays you directly
"""
from hippolyzer.lib.base.datatypes import UUID
from hippolyzer.lib.base.message.message import Block
from hippolyzer.lib.base.message.message import Block, Message
from hippolyzer.lib.base.templates import MoneyTransactionType, PCode, ChatType
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.proxy.addon_utils import send_chat, BaseAddon
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
class PaydayAddon(BaseAddon):
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: ProxiedMessage):
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: Message):
if message.name != "MoneyBalanceReply":
return
transaction_block = message["TransactionInfo"][0]
@@ -38,7 +37,7 @@ class PaydayAddon(BaseAddon):
chat_type=ChatType.SHOUT,
)
# Do the traditional money dance.
session.main_region.circuit.send_message(ProxiedMessage(
session.main_region.circuit.send_message(Message(
"AgentAnimation",
Block("AgentData", AgentID=session.agent_id, SessionID=session.id),
Block("AnimationList", AnimID=UUID("928cae18-e31d-76fd-9cc9-2f55160ff818"), StartAnim=True),

View File

@@ -18,14 +18,13 @@ from typing import *
from hippolyzer.lib.base import llsd
from hippolyzer.lib.base.datatypes import UUID
from hippolyzer.lib.base.message.message import Block
from hippolyzer.lib.base.message.message import Block, Message
from hippolyzer.lib.base.templates import AssetType, WearableType
from hippolyzer.lib.base.wearables import Wearable, VISUAL_PARAMS
from hippolyzer.lib.proxy.addon_utils import BaseAddon, SessionProperty, AssetAliasTracker, show_message
from hippolyzer.lib.proxy.commands import handle_command
from hippolyzer.lib.proxy.http_flow import HippoHTTPFlow
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.proxy.packets import Direction
from hippolyzer.lib.base.network.transport import Direction
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session, SessionManager
@@ -52,7 +51,7 @@ class RecapitatorAddon(BaseAddon):
self.recapitating = False
show_message("Recapitation disabled")
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: ProxiedMessage):
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: Message):
if not self.recapitating:
return
if message.direction != Direction.OUT:
@@ -68,7 +67,7 @@ class RecapitatorAddon(BaseAddon):
self._schedule_task(self._proxy_bodypart_upload(session, region, new_message))
return True
async def _proxy_bodypart_upload(self, session: Session, region: ProxiedRegion, message: ProxiedMessage):
async def _proxy_bodypart_upload(self, session: Session, region: ProxiedRegion, message: Message):
asset_block = message["AssetBlock"]
# Asset will already be in the viewer's VFS as the expected asset ID, calculate it.
asset_id = session.tid_to_assetid(asset_block["TransactionID"])
@@ -117,7 +116,7 @@ class RecapitatorAddon(BaseAddon):
except:
logging.exception("Exception while recapitating")
# Tell the viewer about the status of its original upload
region.circuit.send_message(ProxiedMessage(
region.circuit.send_message(Message(
"AssetUploadComplete",
Block("AssetBlock", UUID=asset_id, Type=asset_block["Type"], Success=success),
direction=Direction.IN,

View File

@@ -1,12 +1,12 @@
from hippolyzer.lib.proxy.addons import AddonManager
from hippolyzer.lib.proxy.addon_utils import BaseAddon
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
class REPLExampleAddon(BaseAddon):
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: ProxiedMessage):
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: Message):
if message.name == "ChatFromViewer":
chat_msg = message["ChatData"]["Message"]
if not chat_msg:

View File

@@ -15,8 +15,8 @@ from hippolyzer.lib.base import serialization as se
from hippolyzer.lib.base.message.udpdeserializer import UDPMessageDeserializer
from hippolyzer.lib.base.message.udpserializer import UDPMessageSerializer
from hippolyzer.lib.proxy.addon_utils import BaseAddon
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.proxy.packets import ProxiedUDPPacket
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.base.network.transport import UDPPacket
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import SessionManager, Session
@@ -28,9 +28,9 @@ class SerializationSanityChecker(BaseAddon):
self.serializer = UDPMessageSerializer()
self.deserializer = UDPMessageDeserializer()
def handle_proxied_packet(self, session_manager: SessionManager, packet: ProxiedUDPPacket,
def handle_proxied_packet(self, session_manager: SessionManager, packet: UDPPacket,
session: Optional[Session], region: Optional[ProxiedRegion],
message: Optional[ProxiedMessage]):
message: Optional[Message]):
# Well this doesn't even parse as a message, can't do anything about it.
if message is None:
LOG.error(f"Received unparseable message from {packet.src_addr!r}: {packet.data!r}")
@@ -63,7 +63,7 @@ class SerializationSanityChecker(BaseAddon):
except:
LOG.exception(f"Exception during message validation:\n{message!r}")
def _roundtrip_var_serializers(self, message: ProxiedMessage):
def _roundtrip_var_serializers(self, message: Message):
for block in itertools.chain(*message.blocks.values()):
for var_name in block.vars.keys():
orig_val = block[var_name]

View File

@@ -1,8 +1,8 @@
"""Block potentially bad things"""
from hippolyzer.lib.base.templates import IMDialogType, XferFilePath
from hippolyzer.lib.proxy.addon_utils import BaseAddon, show_message
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.proxy.packets import Direction
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.base.network.transport import Direction
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
@@ -11,7 +11,7 @@ REGULAR_IM_DIALOGS = (IMDialogType.TYPING_STOP, IMDialogType.TYPING_STOP, IMDial
class ShieldAddon(BaseAddon):
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: ProxiedMessage):
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: Message):
if message.direction != Direction.IN:
return
if message.name in SUSPICIOUS_PACKETS:

View File

@@ -1,6 +1,6 @@
import itertools
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
@@ -12,7 +12,7 @@ def _to_spongecase(val):
return "".join(itertools.chain(*spongecased))
def handle_lludp_message(session: Session, _region: ProxiedRegion, message: ProxiedMessage):
def handle_lludp_message(session: Session, _region: ProxiedRegion, message: Message):
ctx = session.addon_ctx
ctx.setdefault("spongecase", False)
if message.name == "ChatFromViewer":

View File

@@ -4,7 +4,7 @@ Example of how to request a Transfer
from typing import *
from hippolyzer.lib.base.legacy_inv import InventoryModel, InventoryItem
from hippolyzer.lib.base.message.message import Block
from hippolyzer.lib.base.message.message import Block, Message
from hippolyzer.lib.base.templates import (
AssetType,
EstateAssetType,
@@ -15,7 +15,6 @@ from hippolyzer.lib.base.templates import (
)
from hippolyzer.lib.proxy.addon_utils import BaseAddon, show_message
from hippolyzer.lib.proxy.commands import handle_command
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
@@ -36,7 +35,7 @@ class TransferExampleAddon(BaseAddon):
async def get_first_script(self, session: Session, region: ProxiedRegion):
"""Get the contents of the first script in the selected object"""
# Ask for the object inventory so we can find a script
region.circuit.send_message(ProxiedMessage(
region.circuit.send_message(Message(
'RequestTaskInventory',
Block('AgentData', AgentID=session.agent_id, SessionID=session.id),
Block('InventoryData', LocalID=session.selected.object_local),

View File

@@ -34,15 +34,15 @@ from typing import *
from hippolyzer.lib.base.templates import XferFilePath
from hippolyzer.lib.proxy.addon_utils import BaseAddon
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.proxy.packets import Direction
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.base.network.transport import Direction
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
from hippolyzer.lib.proxy.xfer_manager import Xfer
class TurboObjectInventoryAddon(BaseAddon):
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: ProxiedMessage):
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: Message):
if message.direction != Direction.OUT:
return
if message.name != "RequestTaskInventory":
@@ -54,7 +54,7 @@ class TurboObjectInventoryAddon(BaseAddon):
async def _proxy_task_inventory_request(
self,
region: ProxiedRegion,
request_msg: ProxiedMessage
request_msg: Message
):
# Keep around a dict of chunks we saw previously in case we have to restart
# an Xfer due to missing chunks. We don't expect chunks to change across Xfers

View File

@@ -11,13 +11,12 @@ from typing import *
import aiohttp
from hippolyzer.lib.base.datatypes import UUID
from hippolyzer.lib.base.message.message import Block
from hippolyzer.lib.base.message.message import Block, Message
from hippolyzer.lib.base.templates import AssetType
from hippolyzer.lib.proxy.addons import AddonManager
from hippolyzer.lib.proxy.addon_utils import ais_item_to_inventory_data, show_message, BaseAddon
from hippolyzer.lib.proxy.commands import handle_command, Parameter
from hippolyzer.lib.proxy.packets import Direction
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.base.network.transport import Direction
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
@@ -92,7 +91,7 @@ class UploaderAddon(BaseAddon):
async with region.caps_client.post('FetchInventory2', llsd=ais_req_data) as resp:
ais_item = (await resp.read_llsd())["items"][0]
message = ProxiedMessage(
message = Message(
"UpdateCreateInventoryItem",
Block(
"AgentData",

View File

@@ -4,10 +4,9 @@ Example of how to request an Xfer
from hippolyzer.lib.base.datatypes import UUID
from hippolyzer.lib.base.legacy_inv import InventoryModel
from hippolyzer.lib.base.templates import XferFilePath, AssetType, InventoryType, WearableType
from hippolyzer.lib.base.message.message import Block
from hippolyzer.lib.base.message.message import Block, Message
from hippolyzer.lib.proxy.addon_utils import BaseAddon, show_message
from hippolyzer.lib.proxy.commands import handle_command
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
@@ -16,7 +15,7 @@ class XferExampleAddon(BaseAddon):
@handle_command()
async def get_mute_list(self, session: Session, region: ProxiedRegion):
"""Fetch the current user's mute list"""
region.circuit.send_message(ProxiedMessage(
region.circuit.send_message(Message(
'MuteListRequest',
Block('AgentData', AgentID=session.agent_id, SessionID=session.id),
Block("MuteData", MuteCRC=0),
@@ -36,7 +35,7 @@ class XferExampleAddon(BaseAddon):
@handle_command()
async def get_task_inventory(self, session: Session, region: ProxiedRegion):
"""Get the inventory of the currently selected object"""
region.circuit.send_message(ProxiedMessage(
region.circuit.send_message(Message(
'RequestTaskInventory',
# If no session is passed in we'll use the active session when the coro was created
Block('AgentData', AgentID=session.agent_id, SessionID=session.id),
@@ -99,7 +98,7 @@ textures 1
data=asset_data,
transaction_id=transaction_id
)
region.circuit.send_message(ProxiedMessage(
region.circuit.send_message(Message(
'CreateInventoryItem',
Block('AgentData', AgentID=session.agent_id, SessionID=session.id),
Block(

View File

@@ -17,7 +17,7 @@ from hippolyzer.lib.proxy.commands import handle_command
from hippolyzer.lib.proxy.http_proxy import create_http_proxy, create_proxy_master, HTTPFlowContext
from hippolyzer.lib.proxy.http_event_manager import MITMProxyEventManager
from hippolyzer.lib.proxy.lludp_proxy import SLSOCKS5Server
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import SessionManager, Session
@@ -25,7 +25,7 @@ LOG = logging.getLogger(__name__)
class SelectionManagerAddon(BaseAddon):
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: ProxiedMessage):
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: Message):
selected = session.selected
if message.name == "ObjectSelect":
# ObjectDeselect intentionally ignored to deal with messages that

View File

@@ -25,17 +25,17 @@ from hippolyzer.lib.base import llsd
from hippolyzer.lib.base.datatypes import UUID
from hippolyzer.lib.base.helpers import bytes_unescape, bytes_escape, get_resource_filename
from hippolyzer.lib.base.message.llsd_msg_serializer import LLSDMessageSerializer
from hippolyzer.lib.base.message.message import Block
from hippolyzer.lib.base.message.message import Block, Message
from hippolyzer.lib.base.message.msgtypes import MsgType
from hippolyzer.lib.base.message.template_dict import TemplateDictionary
from hippolyzer.lib.base.ui_helpers import loadUi
import hippolyzer.lib.base.serialization as se
from hippolyzer.lib.base.message.message import VerbatimHumanVal, subfield_eval, SpannedString
from hippolyzer.lib.base.network.transport import Direction
from hippolyzer.lib.proxy.addons import BaseInteractionManager, AddonManager
from hippolyzer.lib.proxy.ca_utils import setup_ca_everywhere
from hippolyzer.lib.proxy.caps_client import CapsClient
from hippolyzer.lib.proxy.http_proxy import create_proxy_master, HTTPFlowContext
from hippolyzer.lib.proxy.packets import Direction
from hippolyzer.lib.proxy.message import ProxiedMessage, VerbatimHumanVal, proxy_eval, SpannedString
from hippolyzer.lib.proxy.message_logger import LLUDPMessageLogEntry, AbstractMessageLogEntry
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session, SessionManager
@@ -513,7 +513,7 @@ class MessageBuilderWindow(QtWidgets.QMainWindow):
self.textRequest.clear()
template = self.templateDict[message_name]
msg = ProxiedMessage(message_name, direction=Direction.OUT)
msg = Message(message_name, direction=Direction.OUT)
for tmpl_block in template.blocks:
num_blocks = tmpl_block.number or 1
@@ -609,7 +609,7 @@ class MessageBuilderWindow(QtWidgets.QMainWindow):
env = self._buildEnv(session, region)
# We specifically want to allow `eval()` in messages since
# messages from here are trusted.
msg = ProxiedMessage.from_human_string(msg_text, replacements, env, safe=False)
msg = Message.from_human_string(msg_text, replacements, env, safe=False)
if self.checkLLUDPViaCaps.isChecked():
if msg.direction == Direction.IN:
region.eq_manager.queue_event(
@@ -628,7 +628,7 @@ class MessageBuilderWindow(QtWidgets.QMainWindow):
transport = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
region.circuit.send_message(msg, transport=transport)
def _sendEQMessage(self, session, region: Optional[ProxiedRegion], msg_text: str, replacements: dict):
def _sendEQMessage(self, session, region: Optional[ProxiedRegion], msg_text: str, _replacements: dict):
if not session or not region:
raise RuntimeError("Need a valid session and region to send EQ event")
message_line, _, body = (x.strip() for x in msg_text.partition("\n"))
@@ -696,7 +696,7 @@ class MessageBuilderWindow(QtWidgets.QMainWindow):
elif directive == b"UNESCAPE":
val = unescaped_contents
elif directive == b"EVAL":
val = proxy_eval(contents.decode("utf8").strip(), globals_={**env, **replacements})
val = subfield_eval(contents.decode("utf8").strip(), globals_={**env, **replacements})
val = _coerce_to_bytes(val)
elif directive == b"REPL":
val = _coerce_to_bytes(replacements[contents.decode("utf8").strip()])

View File

@@ -0,0 +1,65 @@
from __future__ import annotations
import datetime as dt
import logging
from typing import *
from ..network.transport import AbstractUDPTransport, UDPPacket, Direction, ADDR_TUPLE
from .message import Block
from .msgtypes import PacketFlags
from .udpserializer import UDPMessageSerializer
from .message import Message
class Circuit:
def __init__(self, near_host: Optional[ADDR_TUPLE], far_host: ADDR_TUPLE, transport):
self.near_host: Optional[ADDR_TUPLE] = near_host
self.host: ADDR_TUPLE = far_host
self.is_alive = True
self.transport: Optional[AbstractUDPTransport] = transport
self.serializer = UDPMessageSerializer()
self.last_packet_at = dt.datetime.now()
self.packet_id_base = 0
def _send_prepared_message(self, message: Message, transport=None):
try:
serialized = self.serializer.serialize(message)
except:
logging.exception(f"Failed to serialize: {message.to_dict()!r}")
raise
return self.send_datagram(serialized, message.direction, transport=transport)
def send_datagram(self, data: bytes, direction: Direction, transport=None):
self.last_packet_at = dt.datetime.now()
src_addr, dst_addr = self.host, self.near_host
if direction == Direction.OUT:
src_addr, dst_addr = self.near_host, self.host
packet = UDPPacket(src_addr, dst_addr, data, direction)
(transport or self.transport).send_packet(packet)
return packet
def prepare_message(self, message: Message):
if message.finalized:
raise RuntimeError(f"Trying to re-send finalized {message!r}")
message.packet_id = self.packet_id_base
self.packet_id_base += 1
if not message.acks:
message.send_flags &= PacketFlags.ACK
message.finalized = True
def send_message(self, message: Message, transport=None):
if self.prepare_message(message):
return self._send_prepared_message(message, transport)
def send_acks(self, to_ack: Sequence[int], direction=Direction.OUT, packet_id=None):
logging.debug("%r acking %r" % (direction, to_ack))
# TODO: maybe tack this onto `.acks` for next message?
message = Message('PacketAck', *[Block('Packets', ID=x) for x in to_ack])
message.packet_id = packet_id
message.direction = direction
message.injected = True
self.send_message(message)
def __repr__(self):
return "<%s %r : %r>" % (self.__class__.__name__, self.near_host, self.host)

View File

@@ -18,20 +18,51 @@ You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
from __future__ import annotations
import ast
import base64
import copy
import enum
import itertools
import logging
import math
import os
import re
import uuid
from typing import *
from .. import serialization as se
from ..datatypes import *
from .msgtypes import PacketFlags
import hippolyzer.lib.base.datatypes
from hippolyzer.lib.base.datatypes import *
import hippolyzer.lib.base.serialization as se
from hippolyzer.lib.base import llsd
from hippolyzer.lib.base.helpers import HippoPrettyPrinter
import hippolyzer.lib.base.templates as templates
from hippolyzer.lib.base.message.msgtypes import MsgBlockType, PacketFlags
from hippolyzer.lib.base.message.template import MessageTemplate
from hippolyzer.lib.base.network.transport import Direction
BLOCK_DICT = Dict[str, "MsgBlockList"]
VAR_TYPE = Union[TupleCoord, bytes, str, float, int, Tuple, UUID]
_TEMPLATES_MTIME = os.stat(templates.__file__).st_mtime
def _maybe_reload_templates():
# Templates may be modified at runtime during development, check
# if they've changed since startup and reload if they have.
global _TEMPLATES_MTIME
templates_mtime = os.stat(templates.__file__).st_mtime
if _TEMPLATES_MTIME is None or _TEMPLATES_MTIME < templates_mtime:
print("Reloading templates")
try:
importlib.reload(templates) # type: ignore
_TEMPLATES_MTIME = templates_mtime
except:
logging.exception("Failed to reload templates!")
class Block:
"""
@@ -159,9 +190,13 @@ class MsgBlockList(List["Block"]):
class Message:
__slots__ = ("name", "send_flags", "_packet_id", "acks", "body_boundaries", "queued",
"offset", "raw_extra", "raw_body", "deserializer", "_blocks", "finalized")
"offset", "raw_extra", "raw_body", "deserializer", "_blocks", "finalized",
"direction", "meta", "injected", "dropped")
def __init__(self, name, *args, packet_id=None, flags=0, acks=None, direction=None):
# TODO: Do this on a timer or something.
_maybe_reload_templates()
def __init__(self, name, *args, packet_id=None, flags=0, acks=None):
self.name = name
self.send_flags = flags
self._packet_id: Optional[int] = packet_id # aka, sequence number
@@ -170,6 +205,7 @@ class Message:
self.body_boundaries = (-1, -1)
self.offset = 0
self.raw_extra = b""
self.direction: Direction = direction if direction is not None else Direction.OUT
# For lazy deserialization
self.raw_body = None
self.deserializer = None
@@ -179,6 +215,9 @@ class Message:
# Whether message is owned by the queue or should be sent immediately
self.queued: bool = False
self._blocks: BLOCK_DICT = {}
self.meta = {}
self.injected = False
self.dropped = False
self.add_blocks(args)
@@ -317,7 +356,7 @@ class Message:
block_reprs = sep.join(x.repr(pretty=pretty) for x in itertools.chain(*self.blocks.values()))
if block_reprs:
block_reprs = sep + block_reprs
return f"{self.name!r}{block_reprs}"
return f"{self.name!r}{block_reprs}, direction=Direction.{self.direction.name}"
def repr(self, pretty=False):
self.ensure_parsed()
@@ -335,6 +374,197 @@ class Message:
message_copy.packet_id = None
return message_copy
def to_human_string(self, replacements=None, beautify=False,
template: Optional[MessageTemplate] = None) -> SpannedString:
replacements = replacements or {}
_maybe_reload_templates()
spans: SpanDict = {}
string = ""
if self.direction is not None:
string += f'{self.direction.name} '
string += self.name
if self.packet_id is not None:
string += f'\n# {self.packet_id}: {PacketFlags(self.send_flags)!r}'
string += f'{", DROPPED" if self.dropped else ""}{", INJECTED" if self.injected else ""}'
if self.extra:
string += f'\n# EXTRA: {self.extra!r}'
string += '\n\n'
for block_name, block_list in self.blocks.items():
block_suffix = ""
if template and template.get_block(block_name).block_type == MsgBlockType.MBT_VARIABLE:
block_suffix = ' # Variable'
for block_num, block in enumerate(block_list):
string += f"[{block_name}]{block_suffix}\n"
for var_name, val in block.items():
start_len = len(string)
string += self._format_var(block, var_name, val, replacements, beautify)
end_len = len(string)
# Store the spans for each var so we can highlight specific matches
spans[(self.name, block_name, block_num, var_name)] = (start_len, end_len)
string += "\n"
spanned = SpannedString(string)
spanned.spans = spans
return spanned
def _format_var(self, block, var_name, var_val, replacements, beautify=False):
string = ""
# Check if we have a more human-readable way to present this field
ser_key = (self.name, block.name, var_name)
serializer = se.SUBFIELD_SERIALIZERS.get(ser_key)
field_prefix = ""
if isinstance(var_val, VerbatimHumanVal):
var_data = var_val
elif isinstance(var_val, (uuid.UUID, TupleCoord)):
var_data = str(var_val)
elif isinstance(var_val, (str, bytes)) and not serializer:
var_data = self._multi_line_pformat(var_val)
else:
var_data = repr(var_val)
if serializer and beautify and not isinstance(var_val, VerbatimHumanVal):
try:
pretty_data = serializer.deserialize(block, var_val, pod=True)
if pretty_data is not se.UNSERIALIZABLE:
string += f" {var_name} =| {self._multi_line_pformat(pretty_data)}"
if serializer.AS_HEX and isinstance(var_val, int):
var_data = hex(var_val)
if serializer.ORIG_INLINE:
string += f" #{var_data}"
return string
else:
string += "\n"
# Human-readable version should be used, orig data is commented out
field_prefix = "#"
except:
logging.exception(f"Failed in subfield serializer {ser_key!r}")
if beautify:
if block.name == "AgentData":
if var_name == "AgentID" and var_val == replacements.get("AGENT_ID"):
var_data = "[[AGENT_ID]]"
elif var_name == "SessionID" and var_val == replacements.get("SESSION_ID"):
var_data = "[[SESSION_ID]]"
if "CircuitCode" in var_name or ("Code" in var_name and "Circuit" in block.name):
if var_val == replacements.get("CIRCUIT_CODE"):
var_data = "[[CIRCUIT_CODE]]"
string += f" {field_prefix}{var_name} = {var_data}"
return string
@staticmethod
def _multi_line_pformat(val):
printer = HippoPrettyPrinter(width=100)
val = printer.pformat(val)
newstr = ""
# Now we need to rebuild this to add in the appropriate
# line continuations.
lines = list(val.splitlines())
first_line = True
while lines:
line = lines.pop(0)
prefix = ""
suffix = ""
if first_line:
first_line = False
else:
prefix = " "
if lines:
suffix = " \\\n"
newstr += f"{prefix}{line}{suffix}"
return newstr
def to_summary(self):
string = ""
for block_name, block_list in self.blocks.items():
for block in block_list:
for var_name, val in block.items():
if block.name == "AgentData" and var_name in ("AgentID", "SessionID"):
continue
if string:
string += ", "
string += f"{var_name}={_trunc_repr(val, 10)}"
return string
@classmethod
def from_human_string(cls, string, replacements=None, env=None, safe=True):
_maybe_reload_templates()
replacements = replacements or {}
env = env or {}
first_line = True
cur_block = None
msg = None
lines = [x.strip() for x in string.split("\n") if x.strip()]
while lines:
line = lines.pop(0)
# Ignore comment / blank lines
if re.match(r"^\s*(#.*)?$", line):
continue
if first_line:
direction, message_name = line.split(" ", 1)
msg = cls(message_name)
msg.direction = Direction[direction.upper()]
first_line = False
continue
if line.startswith("["):
cur_block = Block(re.search(r"\w+", line).group(0))
msg.add_block(cur_block)
else:
expr_match = re.match(r"^\s*(\w+)\s*(=[|$]*)\s*(.*)$", line)
var_name, operator, var_val = expr_match.groups()
# Multiline, eat all the line continuations
while var_val.endswith("\\"):
var_val = var_val[:-1].rstrip()
if lines:
var_val += lines.pop(0)
plain = operator == "="
packed = "|" in operator
evaled = "$" in operator
if evaled and safe:
raise ValueError("Can't use eval operator in safe mode")
if plain:
replacement_match = re.match(r"\[\[(\w+)]]", var_val)
if replacement_match:
replacement_name = replacement_match.group(1)
var_val = replacements.get(replacement_name)
if var_val is None:
raise ValueError("Tried to use undefined replacement %s" % replacement_name)
if callable(var_val):
var_val = var_val()
# alternate way of specifying a vector or quat
elif var_val.startswith("<"):
var_val = re.sub(r"[<>]", "", var_val)
var_val = tuple(float(x) for x in var_val.split(","))
# UUID-ish
elif re.match(r"\A\w+-\w+-.*", var_val):
var_val = UUID(var_val)
else:
var_val = ast.literal_eval(var_val)
# Normally gross, but necessary for expressiveness in built messages
# unless a metalanguage is added.
if evaled:
var_val = subfield_eval(
var_val,
globals_={**env, **replacements},
locals_={"block": cur_block}
)
# Using an packer specific to this message
if packed:
if not evaled:
var_val = ast.literal_eval(var_val)
ser_key = (msg.name, cur_block.name, var_name)
serializer = se.SUBFIELD_SERIALIZERS.get(ser_key)
if not serializer:
raise KeyError(f"No subfield serializer for {ser_key!r}")
var_val = serializer.serialize(cur_block, var_val)
cur_block[var_name] = var_val
return msg
def __repr__(self):
return self.repr()
@@ -342,3 +572,45 @@ class Message:
if not isinstance(other, self.__class__):
return NotImplemented
return self.to_dict() == other.to_dict()
def _trunc_repr(val, max_len):
if isinstance(val, (uuid.UUID, TupleCoord)):
val = str(val)
repr_val = repr(val)
if isinstance(val, str):
repr_val = repr_val[1:-1]
if isinstance(val, bytes):
repr_val = repr_val[2:-1]
if len(repr_val) > max_len:
return repr_val[:max_len] + ""
return repr_val
class VerbatimHumanVal(str):
pass
def _filtered_exports(mod):
return {k: getattr(mod, k) for k in mod.__all__}
def subfield_eval(eval_str: str, globals_=None, locals_=None):
return eval(
eval_str,
{
"llsd": llsd,
"base64": base64,
"math": math,
**_filtered_exports(hippolyzer.lib.base.datatypes),
**(globals_ or {})},
locals_
)
TextSpan = Tuple[int, int]
SpanDict = Dict[Tuple[Union[str, int], ...], TextSpan]
class SpannedString(str):
spans: SpanDict = {}

View File

@@ -64,10 +64,9 @@ def _parse_msg_num(reader: se.BufferReader):
class UDPMessageDeserializer:
DEFAULT_TEMPLATE = TemplateDictionary()
def __init__(self, settings=None, message_cls: Type[Message] = Message):
def __init__(self, settings=None):
self.settings = settings or Settings()
self.template_dict = self.DEFAULT_TEMPLATE
self.message_cls = message_cls
def deserialize(self, msg_buff: bytes):
msg = self._parse_message_header(msg_buff)
@@ -85,7 +84,7 @@ class UDPMessageDeserializer:
reader = se.BufferReader("!", data)
msg: Message = self.message_cls("Placeholder")
msg: Message = Message("Placeholder")
msg.send_flags = reader.read(se.U8)
msg.packet_id = reader.read(se.U32)

View File

@@ -0,0 +1,71 @@
import abc
import asyncio
import enum
import typing
ADDR_TUPLE = typing.Tuple[str, int]
class Direction(enum.Enum):
OUT = enum.auto()
IN = enum.auto()
def __invert__(self):
if self == self.OUT:
return self.IN
return self.OUT
class UDPPacket:
def __init__(
self,
src_addr: typing.Optional[ADDR_TUPLE],
dst_addr: ADDR_TUPLE,
data: bytes,
direction: Direction
):
self.src_addr = src_addr
self.dst_addr = dst_addr
self.data = data
self.direction = direction
@property
def outgoing(self):
return self.direction == Direction.OUT
@property
def incoming(self):
return self.direction == Direction.IN
@property
def far_addr(self):
if self.outgoing:
return self.dst_addr
return self.src_addr
class AbstractUDPTransport(abc.ABC):
__slots__ = ()
@abc.abstractmethod
def send_packet(self, packet: UDPPacket) -> None:
pass
@abc.abstractmethod
def close(self) -> None:
pass
class WrappingUDPTransport(AbstractUDPTransport):
def __init__(self, transport: asyncio.DatagramTransport):
super().__init__()
self.transport = transport
def send_packet(self, packet: UDPPacket) -> None:
if not packet.outgoing:
raise ValueError(f"{self.__class__.__name__} can only send outbound packets")
self.transport.sendto(packet.data, packet.dst_addr)
def close(self) -> None:
self.transport.close()

View File

@@ -8,13 +8,12 @@ import warnings
from typing import *
from hippolyzer.lib.base.datatypes import UUID, Vector3
from hippolyzer.lib.base.message.message import Block
from hippolyzer.lib.base.message.message import Block, Message
from hippolyzer.lib.base.objects import Object
from hippolyzer.lib.proxy import addon_ctx
from hippolyzer.lib.proxy.addons import AddonManager
from hippolyzer.lib.proxy.http_flow import HippoHTTPFlow
from hippolyzer.lib.proxy.packets import Direction, ProxiedUDPPacket
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.base.network.transport import UDPPacket, Direction
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import SessionManager, Session
from hippolyzer.lib.proxy.task_scheduler import TaskLifeScope
@@ -58,7 +57,7 @@ def show_message(text, session=None) -> None:
# `or None` so we don't use a dead weakref Proxy which are False-y
session = session or addon_ctx.session.get(None) or None
message = ProxiedMessage(
message = Message(
"ChatFromSimulator",
Block(
"ChatData",
@@ -84,7 +83,7 @@ def send_chat(message: Union[bytes, str], channel=0, chat_type=ChatType.NORMAL,
session = session or addon_ctx.session.get(None) or None
if not session:
raise RuntimeError("Tried to send chat without session")
session.main_region.circuit.send_message(ProxiedMessage(
session.main_region.circuit.send_message(Message(
"ChatFromViewer",
Block(
"AgentData",
@@ -160,7 +159,7 @@ class BaseAddon(abc.ABC):
def handle_unload(self, session_manager: SessionManager):
pass
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: ProxiedMessage):
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: Message):
pass
def handle_http_request(self, session_manager: SessionManager, flow: HippoHTTPFlow):
@@ -186,9 +185,9 @@ class BaseAddon(abc.ABC):
cmd: str, options: List[str], param: str):
pass
def handle_proxied_packet(self, session_manager: SessionManager, packet: ProxiedUDPPacket,
def handle_proxied_packet(self, session_manager: SessionManager, packet: UDPPacket,
session: Optional[Session], region: Optional[ProxiedRegion],
message: Optional[ProxiedMessage]):
message: Optional[Message]):
pass

View File

@@ -22,9 +22,9 @@ from hippolyzer.lib.proxy.task_scheduler import TaskLifeScope, TaskScheduler
if TYPE_CHECKING:
from hippolyzer.lib.proxy.commands import CommandDetails, WrappedCommandCallable
from hippolyzer.lib.proxy.http_flow import HippoHTTPFlow
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.proxy.objects import Object
from hippolyzer.lib.proxy.packets import ProxiedUDPPacket
from hippolyzer.lib.base.network.transport import UDPPacket
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session, SessionManager
@@ -385,7 +385,7 @@ class AddonManager:
LOG.error(text)
@classmethod
def handle_lludp_message(cls, session: Session, region: ProxiedRegion, message: ProxiedMessage):
def handle_lludp_message(cls, session: Session, region: ProxiedRegion, message: Message):
cls._reload_addons()
if message.name == "ChatFromViewer" and "ChatData" in message:
if message["ChatData"]["Channel"] == cls.COMMAND_CHANNEL:
@@ -517,8 +517,8 @@ class AddonManager:
return cls._call_all_addon_hooks("handle_region_changed", session, region)
@classmethod
def handle_proxied_packet(cls, session_manager: SessionManager, packet: ProxiedUDPPacket,
def handle_proxied_packet(cls, session_manager: SessionManager, packet: UDPPacket,
session: Optional[Session], region: Optional[ProxiedRegion],
message: Optional[ProxiedMessage]):
message: Optional[Message]):
return cls._call_all_addon_hooks("handle_proxied_packet", session_manager,
packet, session, region, message)

View File

@@ -1,36 +1,25 @@
from __future__ import annotations
import asyncio
import datetime as dt
import logging
from collections import deque
from typing import *
from hippolyzer.lib.base.message.message import Block
from hippolyzer.lib.base.message.circuit import Circuit
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.base.message.msgtypes import PacketFlags
from hippolyzer.lib.base.message.udpserializer import UDPMessageSerializer
from hippolyzer.lib.proxy.packets import Direction, ProxiedUDPPacket
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.base.network.transport import Direction
LLUDP_LOGGING_HOOK = Optional[Callable[[Message], Any]]
LLUDP_LOGGING_HOOK = Optional[Callable[[ProxiedMessage], Any]]
class ProxiedCircuit:
def __init__(self, near_host, far_host, transport, logging_hook: LLUDP_LOGGING_HOOK = None,
socks_transport: Optional[bool] = None):
self.near_host = near_host
self.host = far_host
self.is_alive = True
self.socks_transport = socks_transport
self.transport: Optional[asyncio.DatagramTransport] = transport
class ProxiedCircuit(Circuit):
def __init__(self, near_host, far_host, transport, logging_hook: LLUDP_LOGGING_HOOK = None):
super().__init__(near_host, far_host, transport)
self.in_injections = InjectionTracker(0)
self.out_injections = InjectionTracker(0)
self.serializer = UDPMessageSerializer()
self.last_packet_at = dt.datetime.now()
self.logging_hook: LLUDP_LOGGING_HOOK = logging_hook
def _send_prepared_message(self, message: ProxiedMessage, direction, transport=None):
def _send_prepared_message(self, message: Message, transport=None):
try:
serialized = self.serializer.serialize(message)
except:
@@ -38,25 +27,14 @@ class ProxiedCircuit:
raise
if self.logging_hook and message.injected:
self.logging_hook(message)
return self.send_datagram(serialized, direction, transport=transport)
def send_datagram(self, data: bytes, direction: Direction, transport=None):
self.last_packet_at = dt.datetime.now()
src_addr, dst_addr = self.host, self.near_host
if direction == Direction.OUT:
src_addr, dst_addr = self.near_host, self.host
packet = ProxiedUDPPacket(src_addr, dst_addr, data, direction)
packet_data = packet.serialize(socks_header=self.socks_transport)
(transport or self.transport).sendto(packet_data, dst_addr)
return packet
return self.send_datagram(serialized, message.direction, transport=transport)
def _get_injections(self, direction: Direction):
if direction == Direction.OUT:
return self.out_injections, self.in_injections
return self.in_injections, self.out_injections
def prepare_message(self, message: ProxiedMessage, direction=None):
def prepare_message(self, message: Message, direction=None):
if message.finalized:
raise RuntimeError(f"Trying to re-send finalized {message!r}")
direction = direction or getattr(message, 'direction')
@@ -97,12 +75,7 @@ class ProxiedCircuit:
message.send_flags &= ~PacketFlags.ACK
return True
def send_message(self, message: ProxiedMessage, direction=None, transport=None):
direction = direction or getattr(message, 'direction')
if self.prepare_message(message, direction):
return self._send_prepared_message(message, direction, transport)
def _rewrite_packet_ack(self, message: ProxiedMessage, reverse_injections):
def _rewrite_packet_ack(self, message: Message, reverse_injections):
new_blocks = []
for block in message["Packets"]:
packet_id = block["ID"]
@@ -119,14 +92,14 @@ class ProxiedCircuit:
message["Packets"] = new_blocks
return True
def _rewrite_start_ping_check(self, message: ProxiedMessage, fwd_injections):
def _rewrite_start_ping_check(self, message: Message, fwd_injections):
orig_id = message["PingID"]["OldestUnacked"]
new_id = fwd_injections.get_effective_id(orig_id)
if orig_id != new_id:
logging.debug("Rewrote oldest unacked %s -> %s" % (orig_id, new_id))
message["PingID"]["OldestUnacked"] = new_id
def drop_message(self, message: ProxiedMessage, orig_direction=None):
def drop_message(self, message: Message, orig_direction=None):
if message.finalized:
raise RuntimeError(f"Trying to drop finalized {message!r}")
if message.packet_id is None:
@@ -135,13 +108,12 @@ class ProxiedCircuit:
fwd_injections, reverse_injections = self._get_injections(orig_direction)
fwd_injections.mark_dropped(message.packet_id)
if hasattr(message, 'dropped'):
message.dropped = True
message.dropped = True
message.finalized = True
# Was sent reliably, tell the other end that we saw it and to shut up.
if message.reliable:
self._send_acks([message.packet_id], ~orig_direction)
self.send_acks([message.packet_id], ~orig_direction)
# This packet had acks for the other end, send them in a separate PacketAck
effective_acks = tuple(
@@ -149,20 +121,7 @@ class ProxiedCircuit:
if not reverse_injections.was_injected(x)
)
if effective_acks:
self._send_acks(effective_acks, orig_direction, packet_id=message.packet_id)
def _send_acks(self, to_ack, direction, packet_id=None):
logging.debug("%r acking %r" % (direction, to_ack))
# TODO: maybe tack this onto `.acks` for next message?
packet = ProxiedMessage('PacketAck',
*[Block('Packets', ID=x) for x in to_ack])
packet.packet_id = packet_id
packet.injected = True
packet.direction = direction
self.send_message(packet)
def __repr__(self):
return "<%s %r : %r>" % (self.__class__.__name__, self.near_host, self.host)
self.send_acks(effective_acks, orig_direction, packet_id=message.packet_id)
class InjectionTracker:

View File

@@ -7,8 +7,8 @@ from hippolyzer.lib.base.message.udpdeserializer import UDPMessageDeserializer
from hippolyzer.lib.base.message.udpserializer import UDPMessageSerializer
from hippolyzer.lib.base.settings import Settings
from hippolyzer.lib.proxy.addons import AddonManager
from hippolyzer.lib.proxy.packets import ProxiedUDPPacket
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.base.network.transport import UDPPacket
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session, SessionManager
from hippolyzer.lib.proxy.socks_proxy import SOCKS5Server, UDPProxyProtocol
@@ -35,11 +35,10 @@ class BaseLLUDPProxyProtocol(UDPProxyProtocol):
self.serializer = UDPMessageSerializer()
self.deserializer = UDPMessageDeserializer(
settings=self.settings,
message_cls=ProxiedMessage,
)
self.message_xml = MessageDotXML()
def _ensure_message_allowed(self, msg: ProxiedMessage):
def _ensure_message_allowed(self, msg: Message):
if not self.message_xml.validate_udp_msg(msg.name):
LOG.warning(
f"Received {msg.name!r} over UDP, when it should come over the event queue. Discarding."
@@ -53,8 +52,8 @@ class InterceptingLLUDPProxyProtocol(BaseLLUDPProxyProtocol):
self.session_manager: SessionManager = session_manager
self.session: Optional[Session] = None
def _handle_proxied_packet(self, packet: ProxiedUDPPacket):
message: Optional[ProxiedMessage] = None
def _handle_proxied_packet(self, packet: UDPPacket):
message: Optional[Message] = None
region: Optional[ProxiedRegion] = None
# Try to do an initial region lookup so we have it for handle_proxied_packet()
if self.session:

View File

@@ -1,286 +0,0 @@
import ast
import base64
import importlib
import logging
import math
import os
import re
import uuid
from typing import *
import hippolyzer.lib.base.datatypes
from hippolyzer.lib.base.datatypes import *
import hippolyzer.lib.base.serialization as se
from hippolyzer.lib.base import llsd
from hippolyzer.lib.base.helpers import HippoPrettyPrinter
from hippolyzer.lib.base.message.message import Message, Block, PacketFlags
import hippolyzer.lib.base.templates as templates
from hippolyzer.lib.base.message.msgtypes import MsgBlockType
from hippolyzer.lib.base.message.template import MessageTemplate
from hippolyzer.lib.proxy.packets import Direction
_TEMPLATES_MTIME = os.stat(templates.__file__).st_mtime
def _maybe_reload_templates():
# Templates may be modified at runtime during development, check
# if they've changed since startup and reload if they have.
global _TEMPLATES_MTIME
templates_mtime = os.stat(templates.__file__).st_mtime
if _TEMPLATES_MTIME is None or _TEMPLATES_MTIME < templates_mtime:
print("Reloading templates")
try:
importlib.reload(templates) # type: ignore
_TEMPLATES_MTIME = templates_mtime
except:
logging.exception("Failed to reload templates!")
def _trunc_repr(val, max_len):
if isinstance(val, (uuid.UUID, TupleCoord)):
val = str(val)
repr_val = repr(val)
if isinstance(val, str):
repr_val = repr_val[1:-1]
if isinstance(val, bytes):
repr_val = repr_val[2:-1]
if len(repr_val) > max_len:
return repr_val[:max_len] + ""
return repr_val
class VerbatimHumanVal(str):
pass
def _filtered_exports(mod):
return {k: getattr(mod, k) for k in mod.__all__}
def proxy_eval(eval_str: str, globals_=None, locals_=None):
return eval(
eval_str,
{
"llsd": llsd,
"base64": base64,
"math": math,
**_filtered_exports(hippolyzer.lib.base.datatypes),
**(globals_ or {})},
locals_
)
TextSpan = Tuple[int, int]
SpanDict = Dict[Tuple[Union[str, int], ...], TextSpan]
class SpannedString(str):
spans: SpanDict = {}
class ProxiedMessage(Message):
__slots__ = ("meta", "injected", "dropped", "direction")
def __init__(self, *args, direction=None, **kwargs):
super().__init__(*args, **kwargs)
self.direction = direction if direction is not None else Direction.OUT
self.meta = {}
self.injected = False
self.dropped = False
_maybe_reload_templates()
def to_human_string(self, replacements=None, beautify=False,
template: Optional[MessageTemplate] = None) -> SpannedString:
replacements = replacements or {}
_maybe_reload_templates()
spans: SpanDict = {}
string = ""
if self.direction is not None:
string += f'{self.direction.name} '
string += self.name
if self.packet_id is not None:
string += f'\n# {self.packet_id}: {PacketFlags(self.send_flags)!r}'
string += f'{", DROPPED" if self.dropped else ""}{", INJECTED" if self.injected else ""}'
if self.extra:
string += f'\n# EXTRA: {self.extra!r}'
string += '\n\n'
for block_name, block_list in self.blocks.items():
block_suffix = ""
if template and template.get_block(block_name).block_type == MsgBlockType.MBT_VARIABLE:
block_suffix = ' # Variable'
for block_num, block in enumerate(block_list):
string += f"[{block_name}]{block_suffix}\n"
for var_name, val in block.items():
start_len = len(string)
string += self._format_var(block, var_name, val, replacements, beautify)
end_len = len(string)
# Store the spans for each var so we can highlight specific matches
spans[(self.name, block_name, block_num, var_name)] = (start_len, end_len)
string += "\n"
spanned = SpannedString(string)
spanned.spans = spans
return spanned
def _format_var(self, block, var_name, var_val, replacements, beautify=False):
string = ""
# Check if we have a more human-readable way to present this field
ser_key = (self.name, block.name, var_name)
serializer = se.SUBFIELD_SERIALIZERS.get(ser_key)
field_prefix = ""
if isinstance(var_val, VerbatimHumanVal):
var_data = var_val
elif isinstance(var_val, (uuid.UUID, TupleCoord)):
var_data = str(var_val)
elif isinstance(var_val, (str, bytes)) and not serializer:
var_data = self._multi_line_pformat(var_val)
else:
var_data = repr(var_val)
if serializer and beautify and not isinstance(var_val, VerbatimHumanVal):
try:
pretty_data = serializer.deserialize(block, var_val, pod=True)
if pretty_data is not se.UNSERIALIZABLE:
string += f" {var_name} =| {self._multi_line_pformat(pretty_data)}"
if serializer.AS_HEX and isinstance(var_val, int):
var_data = hex(var_val)
if serializer.ORIG_INLINE:
string += f" #{var_data}"
return string
else:
string += "\n"
# Human-readable version should be used, orig data is commented out
field_prefix = "#"
except:
logging.exception(f"Failed in subfield serializer {ser_key!r}")
if beautify:
if block.name == "AgentData":
if var_name == "AgentID" and var_val == replacements.get("AGENT_ID"):
var_data = "[[AGENT_ID]]"
elif var_name == "SessionID" and var_val == replacements.get("SESSION_ID"):
var_data = "[[SESSION_ID]]"
if "CircuitCode" in var_name or ("Code" in var_name and "Circuit" in block.name):
if var_val == replacements.get("CIRCUIT_CODE"):
var_data = "[[CIRCUIT_CODE]]"
string += f" {field_prefix}{var_name} = {var_data}"
return string
@staticmethod
def _multi_line_pformat(val):
printer = HippoPrettyPrinter(width=100)
val = printer.pformat(val)
newstr = ""
# Now we need to rebuild this to add in the appropriate
# line continuations.
lines = list(val.splitlines())
first_line = True
while lines:
line = lines.pop(0)
prefix = ""
suffix = ""
if first_line:
first_line = False
else:
prefix = " "
if lines:
suffix = " \\\n"
newstr += f"{prefix}{line}{suffix}"
return newstr
def to_summary(self):
string = ""
for block_name, block_list in self.blocks.items():
for block in block_list:
for var_name, val in block.items():
if block.name == "AgentData" and var_name in ("AgentID", "SessionID"):
continue
if string:
string += ", "
string += f"{var_name}={_trunc_repr(val, 10)}"
return string
@classmethod
def from_human_string(cls, string, replacements=None, env=None, safe=True):
_maybe_reload_templates()
replacements = replacements or {}
env = env or {}
first_line = True
cur_block = None
msg = None
lines = [x.strip() for x in string.split("\n") if x.strip()]
while lines:
line = lines.pop(0)
# Ignore comment / blank lines
if re.match(r"^\s*(#.*)?$", line):
continue
if first_line:
direction, message_name = line.split(" ", 1)
msg = ProxiedMessage(message_name)
msg.direction = Direction[direction.upper()]
first_line = False
continue
if line.startswith("["):
cur_block = Block(re.search(r"\w+", line).group(0))
msg.add_block(cur_block)
else:
expr_match = re.match(r"^\s*(\w+)\s*(=[|$]*)\s*(.*)$", line)
var_name, operator, var_val = expr_match.groups()
# Multiline, eat all the line continuations
while var_val.endswith("\\"):
var_val = var_val[:-1].rstrip()
if lines:
var_val += lines.pop(0)
plain = operator == "="
packed = "|" in operator
evaled = "$" in operator
if evaled and safe:
raise ValueError("Can't use eval operator in safe mode")
if plain:
replacement_match = re.match(r"\[\[(\w+)]]", var_val)
if replacement_match:
replacement_name = replacement_match.group(1)
var_val = replacements.get(replacement_name)
if var_val is None:
raise ValueError("Tried to use undefined replacement %s" % replacement_name)
if callable(var_val):
var_val = var_val()
# alternate way of specifying a vector or quat
elif var_val.startswith("<"):
var_val = re.sub(r"[<>]", "", var_val)
var_val = tuple(float(x) for x in var_val.split(","))
# UUID-ish
elif re.match(r"\A\w+-\w+-.*", var_val):
var_val = UUID(var_val)
else:
var_val = ast.literal_eval(var_val)
# Normally gross, but necessary for expressiveness in built messages
# unless a metalanguage is added.
if evaled:
var_val = proxy_eval(
var_val,
globals_={**env, **replacements},
locals_={"block": cur_block}
)
# Using an packer specific to this message
if packed:
if not evaled:
var_val = ast.literal_eval(var_val)
ser_key = (msg.name, cur_block.name, var_name)
serializer = se.SUBFIELD_SERIALIZERS.get(ser_key)
if not serializer:
raise KeyError(f"No subfield serializer for {ser_key!r}")
var_val = serializer.serialize(cur_block, var_val)
cur_block[var_name] = var_val
return msg
def _args_repr(self, pretty=False):
base = super()._args_repr(pretty=pretty)
return f"{base}, direction=Direction.{self.direction.name}"

View File

@@ -21,7 +21,7 @@ from hippolyzer.lib.proxy.region import CapType
if typing.TYPE_CHECKING:
from hippolyzer.lib.proxy.http_flow import HippoHTTPFlow
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
@@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__)
class BaseMessageLogger:
def log_lludp_message(self, session: Session, region: ProxiedRegion, message: ProxiedMessage):
def log_lludp_message(self, session: Session, region: ProxiedRegion, message: Message):
pass
def log_http_response(self, flow: HippoHTTPFlow):
@@ -62,7 +62,7 @@ class FilteringMessageLogger(BaseMessageLogger):
def set_paused(self, paused: bool):
self._paused = paused
def log_lludp_message(self, session: Session, region: ProxiedRegion, message: ProxiedMessage):
def log_lludp_message(self, session: Session, region: ProxiedRegion, message: Message):
if self._paused:
return
self._add_log_entry(LLUDPMessageLogEntry(message, region, session))
@@ -513,8 +513,8 @@ class EQMessageLogEntry(AbstractMessageLogEntry):
class LLUDPMessageLogEntry(AbstractMessageLogEntry):
__slots__ = ["_message", "_name", "_direction", "_frozen_message", "_seq", "_deserializer"]
def __init__(self, message: ProxiedMessage, region, session):
self._message: ProxiedMessage = message
def __init__(self, message: Message, region, session):
self._message: Message = message
self._deserializer = None
self._name = message.name
self._direction = message.direction

View File

@@ -11,7 +11,7 @@ from hippolyzer.lib.proxy.viewer_settings import iter_viewer_cache_dirs
if TYPE_CHECKING:
from hippolyzer.lib.proxy.http_flow import HippoHTTPFlow
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.base.message.message import Message
@dataclasses.dataclass
@@ -42,7 +42,7 @@ class NameCache:
def create_subscriptions(
self,
message_handler: MessageHandler[ProxiedMessage],
message_handler: MessageHandler[Message],
http_message_handler: MessageHandler[HippoHTTPFlow],
):
message_handler.subscribe("UUIDNameReply", self._handle_uuid_name_reply)
@@ -83,7 +83,7 @@ class NameCache:
entry.display_name = vals["DisplayName"] if vals["DisplayName"] else None
self._cache[uuid] = entry
def _handle_uuid_name_reply(self, msg: ProxiedMessage):
def _handle_uuid_name_reply(self, msg: Message):
for block in msg.blocks["UUIDNameBlock"]:
self.update(block["ID"], {
"FirstName": block["FirstName"],

View File

@@ -13,7 +13,7 @@ from typing import *
from hippolyzer.lib.base import llsd
from hippolyzer.lib.base.datatypes import UUID, Vector3
from hippolyzer.lib.base.helpers import proxify
from hippolyzer.lib.base.message.message import Block
from hippolyzer.lib.base.message.message import Block, Message
from hippolyzer.lib.base.objects import (
handle_to_global_pos,
normalize_object_update,
@@ -24,7 +24,6 @@ from hippolyzer.lib.base.objects import (
)
from hippolyzer.lib.proxy.addons import AddonManager
from hippolyzer.lib.proxy.http_flow import HippoHTTPFlow
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.proxy.namecache import NameCache, NameCacheEntry
from hippolyzer.lib.base.templates import PCode, ObjectStateSerializer
from hippolyzer.lib.proxy.vocache import RegionViewerObjectCacheChain
@@ -363,7 +362,7 @@ class ObjectManager:
self.untrack_object(obj)
self._world_objects.handle_object_gone(obj)
def handle_object_update(self, packet: ProxiedMessage):
def handle_object_update(self, packet: Message):
seen_locals = []
for block in packet['ObjectData']:
object_data = normalize_object_update(block, self._region.handle)
@@ -377,7 +376,7 @@ class ObjectManager:
self._track_new_object(obj)
packet.meta["ObjectUpdateIDs"] = tuple(seen_locals)
def handle_terse_object_update(self, packet: ProxiedMessage):
def handle_terse_object_update(self, packet: Message):
seen_locals = []
for block in packet['ObjectData']:
object_data = normalize_terse_object_update(block, self._region.handle)
@@ -397,7 +396,7 @@ class ObjectManager:
packet.meta["ObjectUpdateIDs"] = tuple(seen_locals)
def handle_object_update_cached(self, packet: ProxiedMessage):
def handle_object_update_cached(self, packet: Message):
seen_locals = []
for block in packet['ObjectData']:
seen_locals.append(block["ID"])
@@ -425,7 +424,7 @@ class ObjectManager:
self.missing_locals.add(block["ID"])
packet.meta["ObjectUpdateIDs"] = tuple(seen_locals)
def handle_object_update_compressed(self, packet: ProxiedMessage):
def handle_object_update_compressed(self, packet: Message):
seen_locals = []
for block in packet['ObjectData']:
object_data = normalize_object_update_compressed(block, self._region.handle)
@@ -438,7 +437,7 @@ class ObjectManager:
self._track_new_object(obj)
packet.meta["ObjectUpdateIDs"] = tuple(seen_locals)
def _handle_object_properties_generic(self, packet: ProxiedMessage):
def _handle_object_properties_generic(self, packet: Message):
seen_locals = []
for block in packet["ObjectData"]:
object_properties = dict(block.items())
@@ -465,14 +464,14 @@ class ObjectManager:
obj.ObjectCosts.update(object_costs)
self.run_object_update_hooks(obj, {"ObjectCosts"}, UpdateType.COSTS)
def _handle_kill_object(self, packet: ProxiedMessage):
def _handle_kill_object(self, packet: Message):
seen_locals = []
for block in packet["ObjectData"]:
self._kill_object_by_local_id(block["ID"])
seen_locals.append(block["ID"])
packet.meta["ObjectUpdateIDs"] = tuple(seen_locals)
def _handle_coarse_location_update(self, packet: ProxiedMessage):
def _handle_coarse_location_update(self, packet: Message):
# TODO: This could lead to weird situations when an avatar crosses a
# region border. Might temporarily still have a CoarseLocationUpdate containing
# the avatar in the old region, making the avatar appear to be in both regions.
@@ -549,8 +548,8 @@ class ObjectManager:
*[Block("ObjectData", ObjectLocalID=x) for x in ids_to_req[:100]],
]
# Selecting causes ObjectProperties to be sent
self._region.circuit.send_message(ProxiedMessage("ObjectSelect", blocks))
self._region.circuit.send_message(ProxiedMessage("ObjectDeselect", blocks))
self._region.circuit.send_message(Message("ObjectSelect", blocks))
self._region.circuit.send_message(Message("ObjectDeselect", blocks))
ids_to_req = ids_to_req[100:]
futures = []
@@ -586,7 +585,7 @@ class ObjectManager:
session = self._region.session()
ids_to_req = local_ids
while ids_to_req:
self._region.circuit.send_message(ProxiedMessage(
self._region.circuit.send_message(Message(
"RequestMultipleObjects",
Block("AgentData", AgentID=session.agent_id, SessionID=session.id),
*[Block("ObjectData", CacheMissType=0, ID=x) for x in ids_to_req[:100]],
@@ -619,7 +618,7 @@ class WorldObjectManager:
message_handler.subscribe("ObjectUpdateCached",
self._handle_object_update_cached)
def _wrap_region_update_handler(self, handler: Callable, message: ProxiedMessage):
def _wrap_region_update_handler(self, handler: Callable, message: Message):
"""
Dispatch an ObjectUpdate to a region's handler based on RegionHandle
@@ -632,16 +631,16 @@ class WorldObjectManager:
return
return handler(region.objects, message)
def _handle_object_update(self, message: ProxiedMessage):
def _handle_object_update(self, message: Message):
self._wrap_region_update_handler(ObjectManager.handle_object_update, message)
def _handle_terse_object_update(self, message: ProxiedMessage):
def _handle_terse_object_update(self, message: Message):
self._wrap_region_update_handler(ObjectManager.handle_terse_object_update, message)
def _handle_object_update_compressed(self, message: ProxiedMessage):
def _handle_object_update_compressed(self, message: Message):
self._wrap_region_update_handler(ObjectManager.handle_object_update_compressed, message)
def _handle_object_update_cached(self, message: ProxiedMessage):
def _handle_object_update_cached(self, message: Message):
self._wrap_region_update_handler(ObjectManager.handle_object_update_cached, message)
def handle_new_object(self, obj: Object):

View File

@@ -1,53 +0,0 @@
import enum
import socket
import struct
import typing
class Direction(enum.Enum):
OUT = enum.auto()
IN = enum.auto()
def __invert__(self):
if self == self.OUT:
return self.IN
return self.OUT
ADDR_TUPLE = typing.Tuple[str, int]
class ProxiedUDPPacket:
HEADER_STRUCT = struct.Struct("!HBB4sH")
def __init__(self, src_addr: ADDR_TUPLE, dst_addr: ADDR_TUPLE, data: bytes, direction: Direction):
self.src_addr = src_addr
self.dst_addr = dst_addr
self.data = data
self.direction = direction
@property
def outgoing(self):
return self.direction == Direction.OUT
@property
def incoming(self):
return self.direction == Direction.IN
@property
def far_addr(self):
if self.outgoing:
return self.dst_addr
return self.src_addr
def _make_socks_header(self):
return self.HEADER_STRUCT.pack(
0, 0, 1, socket.inet_aton(self.far_addr[0]), self.far_addr[1])
def serialize(self, socks_header=None):
# Decide whether we need a header based on packet direction
if socks_header is None:
socks_header = self.incoming
if not socks_header:
return self.data
return self._make_socks_header() + self.data

View File

@@ -22,7 +22,7 @@ from hippolyzer.lib.proxy.xfer_manager import XferManager
if TYPE_CHECKING:
from hippolyzer.lib.proxy.sessions import Session
from hippolyzer.lib.proxy.http_flow import HippoHTTPFlow
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.base.message.message import Message
class CapType(enum.Enum):
@@ -57,7 +57,7 @@ class ProxiedRegion:
if seed_cap:
self._caps["Seed"] = (CapType.NORMAL, seed_cap)
self.session: Optional[Callable[[], Session]] = weakref.ref(session)
self.message_handler: MessageHandler[ProxiedMessage] = MessageHandler()
self.message_handler: MessageHandler[Message] = MessageHandler()
self.http_message_handler: MessageHandler[HippoHTTPFlow] = MessageHandler()
self.eq_manager = EventQueueManager(self)
self.caps_client = CapsClient(self)

View File

@@ -21,7 +21,7 @@ from hippolyzer.lib.proxy.region import ProxiedRegion, CapType
if TYPE_CHECKING:
from hippolyzer.lib.proxy.message_logger import BaseMessageLogger
from hippolyzer.lib.proxy.http_flow import HippoHTTPFlow
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.base.message.message import Message
class Session:
@@ -40,7 +40,7 @@ class Session:
self.selected: SelectionModel = SelectionModel()
self.regions: List[ProxiedRegion] = []
self.started_at = datetime.datetime.now()
self.message_handler: MessageHandler[ProxiedMessage] = MessageHandler()
self.message_handler: MessageHandler[Message] = MessageHandler()
self.http_message_handler: MessageHandler[HippoHTTPFlow] = MessageHandler()
self.objects = WorldObjectManager(self)
self._main_region = None

View File

@@ -6,7 +6,8 @@ import socket
import struct
from typing import Optional, List, Tuple
from hippolyzer.lib.proxy.packets import ProxiedUDPPacket, Direction
from hippolyzer.lib.base.network.transport import UDPPacket, Direction
from hippolyzer.lib.proxy.transport import SOCKS5UDPTransport
class SOCKS5Server:
@@ -145,10 +146,10 @@ class UDPProxyProtocol(asyncio.DatagramProtocol):
def __init__(self, source_addr: Tuple[str, int]):
self.socks_client_addr: Tuple[str, int] = source_addr
self.far_to_near_map = {}
self.transport: Optional[asyncio.DatagramTransport] = None
self.transport: Optional[SOCKS5UDPTransport] = None
def connection_made(self, transport):
self.transport = transport
def connection_made(self, transport: asyncio.DatagramTransport):
self.transport = SOCKS5UDPTransport(transport)
def _parse_socks_datagram(self, data):
rsv, frag, address_type = struct.unpack("!HBB", data[:4])
@@ -183,7 +184,7 @@ class UDPProxyProtocol(asyncio.DatagramProtocol):
# this allows us to have source and dest addr on the same IP
# since we expect a send from client->far to happen first
self.far_to_near_map[remote_addr] = source_addr
src_packet = ProxiedUDPPacket(
src_packet = UDPPacket(
src_addr=source_addr,
dst_addr=remote_addr,
data=data,
@@ -198,7 +199,7 @@ class UDPProxyProtocol(asyncio.DatagramProtocol):
logging.warning("Got datagram from unknown host %s:%s" % source_addr)
return
src_packet = ProxiedUDPPacket(
src_packet = UDPPacket(
src_addr=source_addr,
dst_addr=near_addr,
data=data,
@@ -212,7 +213,7 @@ class UDPProxyProtocol(asyncio.DatagramProtocol):
raise
def _handle_proxied_packet(self, packet):
self.transport.sendto(packet.serialize(), packet.dst_addr)
self.transport.send_packet(packet)
def close(self):
logging.info("Closing UDP transport")

View File

@@ -8,10 +8,9 @@ import dataclasses
from typing import *
from hippolyzer.lib.base.datatypes import UUID
from hippolyzer.lib.base.message.message import Block
from hippolyzer.lib.base.message.message import Block, Message
from hippolyzer.lib.base.message.message_handler import MessageHandler
from hippolyzer.lib.proxy.circuit import ProxiedCircuit
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.base.templates import (
TransferRequestParamsBase,
TransferChannelType,
@@ -48,7 +47,7 @@ class Transfer:
def cancelled(self) -> bool:
return self._future.cancelled()
def is_our_message(self, message: ProxiedMessage):
def is_our_message(self, message: Message):
if "TransferData" in message.blocks:
transfer_block = message["TransferData"][0]
else:
@@ -72,7 +71,7 @@ class Transfer:
class TransferManager:
def __init__(
self,
message_handler: MessageHandler[ProxiedMessage],
message_handler: MessageHandler[Message],
circuit: ProxiedCircuit,
agent_id: Optional[UUID] = None,
session_id: Optional[UUID] = None,
@@ -98,7 +97,7 @@ class TransferManager:
if params_dict.get("SessionID", dataclasses.MISSING) is None:
params.SessionID = self._session_id
self._circuit.send_message(ProxiedMessage(
self._circuit.send_message(Message(
'TransferRequest',
Block(
'TransferInfo',
@@ -121,7 +120,7 @@ class TransferManager:
) as get_msg:
while not transfer.done():
try:
msg: ProxiedMessage = await asyncio.wait_for(get_msg(), 5.0)
msg: Message = await asyncio.wait_for(get_msg(), 5.0)
except TimeoutError as e:
transfer.set_exception(e)
return
@@ -139,7 +138,7 @@ class TransferManager:
ConnectionAbortedError("Unknown failure")
)
def _handle_transfer_packet(self, msg: ProxiedMessage, transfer: Transfer):
def _handle_transfer_packet(self, msg: Message, transfer: Transfer):
transfer_block = msg["TransferData"][0]
packet_id: int = transfer_block["Packet"]
packet_data = transfer_block["Data"]
@@ -147,7 +146,7 @@ class TransferManager:
if transfer_block["Status"] == TransferStatus.DONE and not transfer.done():
transfer.mark_done()
def _handle_transfer_info(self, msg: ProxiedMessage, transfer: Transfer):
def _handle_transfer_info(self, msg: Message, transfer: Transfer):
transfer_block = msg["TransferInfo"][0]
transfer.expected_size = transfer_block["Size"]
# Don't re-set if we get a resend of packet 0

View File

@@ -0,0 +1,20 @@
import socket
import struct
from hippolyzer.lib.base.network.transport import WrappingUDPTransport, UDPPacket
class SOCKS5UDPTransport(WrappingUDPTransport):
HEADER_STRUCT = struct.Struct("!HBB4sH")
@classmethod
def serialize(cls, packet: UDPPacket, force_socks_header: bool = False) -> bytes:
# Decide whether we need a header based on packet direction
if packet.outgoing and not force_socks_header:
return packet.data
header = cls.HEADER_STRUCT.pack(
0, 0, 1, socket.inet_aton(packet.far_addr[0]), packet.far_addr[1])
return header + packet.data
def send_packet(self, packet: UDPPacket) -> None:
self.transport.sendto(self.serialize(packet), packet.dst_addr)

View File

@@ -10,12 +10,11 @@ from typing import *
from hippolyzer.lib.base.datatypes import UUID, RawBytes
from hippolyzer.lib.base.message.data_packer import TemplateDataPacker
from hippolyzer.lib.base.message.message import Block
from hippolyzer.lib.base.message.message import Block, Message
from hippolyzer.lib.base.message.message_handler import MessageHandler
from hippolyzer.lib.base.message.msgtypes import MsgType
from hippolyzer.lib.proxy.circuit import ProxiedCircuit
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.proxy.packets import Direction
from hippolyzer.lib.base.network.transport import Direction
from hippolyzer.lib.base.templates import XferPacket, XferFilePath, AssetType, XferError
_XFER_MESSAGES = {"AbortXfer", "ConfirmXferPacket", "RequestXfer", "SendXferPacket"}
@@ -94,7 +93,7 @@ class UploadStrategy(enum.IntEnum):
class XferManager:
def __init__(
self,
message_handler: MessageHandler[ProxiedMessage],
message_handler: MessageHandler[Message],
circuit: ProxiedCircuit,
secure_session_id: Optional[UUID] = None,
):
@@ -114,7 +113,7 @@ class XferManager:
direction: Direction = Direction.OUT,
) -> Xfer:
xfer_id = xfer_id if xfer_id is not None else random.getrandbits(64)
self._circuit.send_message(ProxiedMessage(
self._circuit.send_message(Message(
'RequestXfer',
Block(
'XferID',
@@ -139,7 +138,7 @@ class XferManager:
) as get_msg:
while not xfer.done():
try:
msg: ProxiedMessage = await asyncio.wait_for(get_msg(), 5.0)
msg: Message = await asyncio.wait_for(get_msg(), 5.0)
except asyncio.exceptions.TimeoutError as e:
xfer.set_exception(e)
return
@@ -157,7 +156,7 @@ class XferManager:
ConnectionAbortedError(f"Xfer failed with {xfer.error_code!r}")
)
def _handle_send_xfer_packet(self, msg: ProxiedMessage, xfer: Xfer):
def _handle_send_xfer_packet(self, msg: Message, xfer: Xfer):
# Received a SendXfer for an Xfer we sent ourselves
packet_id: XferPacket = msg["XferID"][0].deserialize_var("Packet")
packet_data = msg["DataPacket"]["Data"]
@@ -178,7 +177,7 @@ class XferManager:
to_ack = range(xfer.next_ackable, ack_max)
xfer.next_ackable = ack_max
for ack_id in to_ack:
self._circuit.send_message(ProxiedMessage(
self._circuit.send_message(Message(
"ConfirmXferPacket",
Block("XferID", ID=xfer.xfer_id, Packet=ack_id),
direction=xfer.direction,
@@ -220,7 +219,7 @@ class XferManager:
else:
inline_data = data
self._circuit.send_message(ProxiedMessage(
self._circuit.send_message(Message(
"AssetUploadRequest",
Block(
"AssetBlock",
@@ -243,12 +242,12 @@ class XferManager:
try:
# Only need to do this if we're using the xfer upload strategy, otherwise all the
# data was already sent in the AssetUploadRequest and we don't expect a RequestXfer.
def request_predicate(request_msg: ProxiedMessage):
def request_predicate(request_msg: Message):
return request_msg["XferID"]["VFileID"] == asset_id
if xfer is not None:
await self.serve_inbound_xfer_request(xfer, request_predicate)
def complete_predicate(complete_msg: ProxiedMessage):
def complete_predicate(complete_msg: Message):
return complete_msg["AssetBlock"]["UUID"] == asset_id
msg = await message_handler.wait_for('AssetUploadComplete', predicate=complete_predicate)
if msg["AssetBlock"]["Success"] == 1:
@@ -262,7 +261,7 @@ class XferManager:
async def serve_inbound_xfer_request(
self,
xfer: Xfer,
request_predicate: Callable[[ProxiedMessage], bool],
request_predicate: Callable[[Message], bool],
wait_for_confirm: bool = True
):
message_handler = self._message_handler
@@ -276,7 +275,7 @@ class XferManager:
chunk = xfer.chunks.pop(packet_id)
# EOF if there are no chunks left
packet_val = XferPacket(PacketID=packet_id, IsEOF=not bool(xfer.chunks))
self._circuit.send_message(ProxiedMessage(
self._circuit.send_message(Message(
"SendXferPacket",
Block("XferID", ID=xfer.xfer_id, Packet_=packet_val),
Block("DataPacket", Data=chunk),

View File

@@ -163,7 +163,7 @@ class TestMessage(unittest.TestCase):
def test_repr(self):
expected_repr = r"""Message('ChatFromViewer',
Block('AgentData', AgentID=UUID('550e8400-e29b-41d4-a716-446655440000'), SessionID=UUID('550e8400-e29b-41d4-a716-446655440000')),
Block('ChatData', Message='Chatting\n', Type=1, Channel=0))"""
Block('ChatData', Message='Chatting\n', Type=1, Channel=0), direction=Direction.OUT)"""
self.assertEqual(expected_repr, repr(self.chat_msg))
@@ -238,3 +238,59 @@ class TestMessageHandlers(unittest.IsolatedAsyncioTestCase):
self.assertTrue(msg.queued)
# Receiving the message unsubscribes
self.assertEqual(len(foo_handlers), 0)
class TestMessageSubfieldSerializers(unittest.TestCase):
def setUp(self):
self.chat_msg = Message(
'ChatFromViewer',
Block('AgentData',
AgentID=UUID('550e8400-e29b-41d4-a716-446655440000'),
SessionID=UUID('550e8400-e29b-41d4-a716-446655440000')),
Block('ChatData', Message="Chatting\n", Type=1, Channel=0))
def test_pretty_repr(self):
expected_repr = r"""Message('ChatFromViewer',
Block('AgentData', AgentID=UUID('550e8400-e29b-41d4-a716-446655440000'), SessionID=UUID('550e8400-e29b-41d4-a716-446655440000')),
Block('ChatData', Message='Chatting\n', Type_=ChatType.NORMAL, Channel=0), direction=Direction.OUT)"""
self.assertEqual(expected_repr, self.chat_msg.repr(pretty=True))
class HumanReadableMessageTests(unittest.TestCase):
def test_basic(self):
val = """
OUT FooMessage
[SomeBlock]
# IGNORE ME
SomeFloat = 1.0
SomeStr = "baz"
SomeVec = <1,1,1>
[OtherBlock]
UUID = 1f4ffb55-022e-49fb-8c63-6f159aed9b24
"""
msg = Message.from_human_string(val)
self.assertEqual(msg.name, "FooMessage")
self.assertEqual(set(msg.blocks.keys()), {"SomeBlock", "OtherBlock"})
self.assertSequenceEqual(msg["SomeBlock"][0]["SomeVec"], (1.0, 1.0, 1.0))
self.assertEqual(msg["OtherBlock"][0]["UUID"], UUID("1f4ffb55-022e-49fb-8c63-6f159aed9b24"))
def test_eval_allowed(self):
val = """
OUT FooMessage
[SomeBlock]
evaled =$ 1+1
"""
msg = Message.from_human_string(val, safe=False)
self.assertEqual(msg["SomeBlock"][0]["evaled"], 2)
def test_eval_disallowed(self):
val = """
OUT FooMessage
[SomeBlock]
evaled =$ 1+1
"""
with self.assertRaises(ValueError):
Message.from_human_string(val)

View File

@@ -4,20 +4,30 @@ import unittest
from hippolyzer.lib.base.datatypes import UUID
from hippolyzer.lib.base.message.udpserializer import UDPMessageSerializer
from hippolyzer.lib.base.network.transport import AbstractUDPTransport, UDPPacket, ADDR_TUPLE
from hippolyzer.lib.proxy.lludp_proxy import InterceptingLLUDPProxyProtocol
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.proxy.packets import ProxiedUDPPacket
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import SessionManager
from hippolyzer.lib.proxy.transport import SOCKS5UDPTransport
class MockTransport(asyncio.DatagramTransport):
class MockTransport(AbstractUDPTransport):
def sendto(self, data: Any, addr: Optional[ADDR_TUPLE] = ...) -> None:
pass
def abort(self) -> None:
pass
def close(self) -> None:
pass
def __init__(self):
super().__init__()
self.packets: List[Tuple[bytes, Tuple[str, int]]] = []
def sendto(self, data: Any, addr=None) -> None:
self.packets.append((data, addr))
def send_packet(self, packet: UDPPacket) -> None:
self.packets.append((packet.data, packet.dst_addr))
class BaseProxyTest(unittest.IsolatedAsyncioTestCase):
@@ -59,8 +69,8 @@ class BaseProxyTest(unittest.IsolatedAsyncioTestCase):
self.session.open_circuit(self.client_addr, region.circuit_addr,
self.protocol.transport)
def _msg_to_datagram(self, msg: ProxiedMessage, src, dst, direction, socks_header=True):
def _msg_to_datagram(self, msg: Message, src, dst, direction, socks_header=True):
serialized = self.serializer.serialize(msg)
packet = ProxiedUDPPacket(src_addr=src, dst_addr=dst, data=serialized,
direction=direction)
return packet.serialize(socks_header=socks_header)
packet = UDPPacket(src_addr=src, dst_addr=dst, data=serialized,
direction=direction)
return SOCKS5UDPTransport.serialize(packet, force_socks_header=socks_header)

View File

@@ -1,6 +1,6 @@
from __future__ import annotations
from hippolyzer.lib.base.message.message import Block
from hippolyzer.lib.base.message.message import Block, Message
from hippolyzer.lib.proxy import addon_ctx
from hippolyzer.lib.proxy.addon_utils import (
BaseAddon,
@@ -10,8 +10,7 @@ from hippolyzer.lib.proxy.addon_utils import (
)
from hippolyzer.lib.proxy.addons import AddonManager
from hippolyzer.lib.proxy.commands import handle_command
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.proxy.packets import Direction
from hippolyzer.lib.base.network.transport import Direction
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
@@ -40,7 +39,7 @@ class AddonIntegrationTests(BaseProxyTest):
AddonManager.shutdown()
def _fake_command(self, command: str) -> None:
msg = ProxiedMessage(
msg = Message(
"ChatFromViewer",
Block("AgentData", AgentID=self.session.agent_id, SessionID=self.session.id),
Block("ChatData", Message=command, Channel=AddonManager.COMMAND_CHANNEL, fill_missing=True),

View File

@@ -9,15 +9,14 @@ from typing import *
import lazy_object_proxy
from hippolyzer.lib.base.datatypes import UUID
from hippolyzer.lib.base.message.message import Block
from hippolyzer.lib.base.message.message import Block, Message
from hippolyzer.lib.base.message.udpdeserializer import UDPMessageDeserializer
from hippolyzer.lib.base.objects import Object
import hippolyzer.lib.base.serialization as se
from hippolyzer.lib.proxy.addon_utils import BaseAddon
from hippolyzer.lib.proxy.addons import AddonManager
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.proxy.message_logger import FilteringMessageLogger, LLUDPMessageLogEntry
from hippolyzer.lib.proxy.packets import ProxiedUDPPacket, Direction
from hippolyzer.lib.base.network.transport import Direction
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
@@ -31,7 +30,7 @@ class MockAddon(BaseAddon):
def handle_session_init(self, session: Session):
self.events.append(("session_init", session.id))
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: ProxiedMessage):
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: Message):
self.events.append(("lludp", session.id, region.circuit_addr, message.name))
if message.name == "UndoLand":
# Simulate a message being taken out of the regular proxying flow
@@ -53,7 +52,7 @@ class LLUDPIntegrationTests(BaseProxyTest):
def setUp(self) -> None:
super().setUp()
self.addon = MockAddon()
self.deserializer = UDPMessageDeserializer(message_cls=ProxiedMessage)
self.deserializer = UDPMessageDeserializer()
AddonManager.init([], self.session_manager, [self.addon])
def _make_objectupdate_compressed(self, localid: Optional[int] = None, handle: Optional[int] = 123):
@@ -92,7 +91,7 @@ class LLUDPIntegrationTests(BaseProxyTest):
async def test_session_claiming(self):
# Need a UseCircuitCode to claim a pending session
msg = ProxiedMessage(
msg = Message(
"UseCircuitCode",
Block("CircuitCode", Code=self.circuit_code, SessionID=self.session.id,
ID=self.session.agent_id),
@@ -110,7 +109,7 @@ class LLUDPIntegrationTests(BaseProxyTest):
async def test_bad_session_unclaimed(self):
# Need a UseCircuitCode to claim a pending session
msg = ProxiedMessage(
msg = Message(
"UseCircuitCode",
Block("CircuitCode", Code=self.circuit_code, SessionID=UUID.random(),
ID=self.session.agent_id),
@@ -127,7 +126,7 @@ class LLUDPIntegrationTests(BaseProxyTest):
async def test_bad_circuit_not_sent(self):
# Need a UseCircuitCode to claim a pending session
msg = ProxiedMessage(
msg = Message(
"UseCircuitCode",
Block("CircuitCode", Code=self.circuit_code, SessionID=self.session.id,
ID=self.session.agent_id),
@@ -162,8 +161,6 @@ class LLUDPIntegrationTests(BaseProxyTest):
data, dst_addr = packets[0]
# Was being sent towards the client
self.assertEqual(dst_addr, self.client_addr)
# Which means it has a SOCKS header we need to ignore
data = data[ProxiedUDPPacket.HEADER_STRUCT.size:]
# The data should not have changed since we haven't injected
# or dropped anything
self.assertEqual(obj_update, data)
@@ -218,7 +215,7 @@ class LLUDPIntegrationTests(BaseProxyTest):
self._setup_default_circuit()
obj_update = self._make_objectupdate_compressed(1234)
self.protocol.datagram_received(obj_update, self.region_addr)
msg = self.serializer.serialize(ProxiedMessage(
msg = self.serializer.serialize(Message(
"UndoLand",
Block("AgentData", AgentID=UUID(), SessionID=UUID()),
direction=Direction.IN,
@@ -234,7 +231,7 @@ class LLUDPIntegrationTests(BaseProxyTest):
message_logger = SimpleMessageLogger()
self.session_manager.message_logger = message_logger
self._setup_default_circuit()
msg = self.serializer.serialize(ProxiedMessage(
msg = self.serializer.serialize(Message(
"UndoLand",
Block("AgentData", AgentID=UUID(), SessionID=UUID()),
direction=Direction.IN,
@@ -256,7 +253,7 @@ class LLUDPIntegrationTests(BaseProxyTest):
def test_roundtrip_objectupdatecompressed(self):
msg_bytes = self._make_objectupdate_compressed()
message: ProxiedMessage = self.deserializer.deserialize(msg_bytes)
message: Message = self.deserializer.deserialize(msg_bytes)
for block in itertools.chain(*message.blocks.values()):
for var_name in block.vars.keys():
orig_val = block[var_name]

View File

@@ -3,12 +3,11 @@ import unittest
from mitmproxy.test import tflow, tutils
from hippolyzer.lib.base.datatypes import Vector3
from hippolyzer.lib.base.message.message import Block
from hippolyzer.lib.base.message.message import Block, Message as Message
from hippolyzer.lib.base.message.udpdeserializer import UDPMessageDeserializer
from hippolyzer.lib.base.settings import Settings
from hippolyzer.lib.proxy.http_flow import HippoHTTPFlow
from hippolyzer.lib.proxy.http_proxy import SerializedCapData
from hippolyzer.lib.proxy.message import ProxiedMessage as Message
from hippolyzer.lib.proxy.message_logger import LLUDPMessageLogEntry, HTTPMessageLogEntry
from hippolyzer.lib.proxy.message_filter import compile_filter
from hippolyzer.lib.proxy.sessions import SessionManager
@@ -113,7 +112,7 @@ class MessageFilterTests(unittest.TestCase):
settings = Settings()
settings.ENABLE_DEFERRED_PACKET_PARSING = False
settings.HANDLE_PACKETS = False
deser = UDPMessageDeserializer(settings=settings, message_cls=Message)
deser = UDPMessageDeserializer(settings=settings)
update_msg = deser.deserialize(OBJECT_UPDATE)
entry = LLUDPMessageLogEntry(update_msg, None, None)
self.assertTrue(self._filter_matches("ObjectUpdate.ObjectData.ObjectData.Position > (88, 41, 25)", entry))

View File

@@ -1,12 +1,9 @@
import unittest
import uuid
from hippolyzer.lib.base.datatypes import UUID
from hippolyzer.lib.base.message.message import Block
from hippolyzer.lib.base.message.message import Block, Message
from hippolyzer.lib.base.message.msgtypes import PacketFlags
from hippolyzer.lib.proxy.circuit import ProxiedCircuit, InjectionTracker
from hippolyzer.lib.proxy.packets import Direction
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.base.network.transport import Direction
class MockedProxyCircuit(ProxiedCircuit):
@@ -19,8 +16,8 @@ class MockedProxyCircuit(ProxiedCircuit):
self.out_injections = InjectionTracker(0, maxlen=10)
self.in_injections = InjectionTracker(0, maxlen=10)
def _send_prepared_message(self, msg: ProxiedMessage, direction, transport=None):
self.sent_simple.append((msg.packet_id, msg.name, direction, msg.injected, msg.acks))
def _send_prepared_message(self, msg: Message, transport=None):
self.sent_simple.append((msg.packet_id, msg.name, msg.direction, msg.injected, msg.acks))
self.sent_msgs.append(msg)
@@ -29,12 +26,12 @@ class PacketIDTests(unittest.TestCase):
self.circuit = MockedProxyCircuit()
def _send_message(self, msg, outgoing=True):
direction = Direction.OUT if outgoing else Direction.IN
return self.circuit.send_message(msg, direction)
msg.direction = Direction.OUT if outgoing else Direction.IN
return self.circuit.send_message(msg)
def test_basic(self):
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=1))
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=2))
self._send_message(Message('ChatFromViewer', packet_id=1))
self._send_message(Message('ChatFromViewer', packet_id=2))
self.assertSequenceEqual(self.circuit.sent_simple, (
(1, "ChatFromViewer", Direction.OUT, False, ()),
@@ -42,9 +39,9 @@ class PacketIDTests(unittest.TestCase):
))
def test_inject(self):
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=1))
self._send_message(ProxiedMessage('ChatFromViewer'))
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=2))
self._send_message(Message('ChatFromViewer', packet_id=1))
self._send_message(Message('ChatFromViewer'))
self._send_message(Message('ChatFromViewer', packet_id=2))
self.assertSequenceEqual(self.circuit.sent_simple, (
(1, "ChatFromViewer", Direction.OUT, False, ()),
@@ -53,17 +50,17 @@ class PacketIDTests(unittest.TestCase):
))
def test_max_injected(self):
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=1))
self._send_message(Message('ChatFromViewer', packet_id=1))
for _ in range(5):
self._send_message(ProxiedMessage('ChatFromViewer'))
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=2))
self._send_message(Message('ChatFromViewer'))
self._send_message(Message('ChatFromViewer', packet_id=2))
self.assertEqual(self.circuit.out_injections.get_original_id(1), 1)
self.assertEqual(self.circuit.out_injections.get_original_id(7), 2)
for _ in range(7):
self._send_message(ProxiedMessage('ChatFromViewer'))
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=3))
self._send_message(Message('ChatFromViewer'))
self._send_message(Message('ChatFromViewer', packet_id=3))
self.assertEqual(len(self.circuit.sent_simple), 15)
@@ -78,10 +75,10 @@ class PacketIDTests(unittest.TestCase):
self.assertEqual(self.circuit.out_injections.get_original_id(15), 3)
def test_inject_hole_in_sequence(self):
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=1))
self._send_message(ProxiedMessage('ChatFromViewer'))
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=4))
self._send_message(ProxiedMessage('ChatFromViewer'))
self._send_message(Message('ChatFromViewer', packet_id=1))
self._send_message(Message('ChatFromViewer'))
self._send_message(Message('ChatFromViewer', packet_id=4))
self._send_message(Message('ChatFromViewer'))
self.assertSequenceEqual(self.circuit.sent_simple, (
(1, "ChatFromViewer", Direction.OUT, False, ()),
@@ -91,9 +88,9 @@ class PacketIDTests(unittest.TestCase):
))
def test_inject_misordered(self):
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=2))
self._send_message(ProxiedMessage('ChatFromViewer'))
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=1))
self._send_message(Message('ChatFromViewer', packet_id=2))
self._send_message(Message('ChatFromViewer'))
self._send_message(Message('ChatFromViewer', packet_id=1))
self.assertSequenceEqual(self.circuit.sent_simple, [
(2, "ChatFromViewer", Direction.OUT, False, ()),
@@ -102,12 +99,12 @@ class PacketIDTests(unittest.TestCase):
])
def test_inject_multiple(self):
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=1))
self._send_message(ProxiedMessage('ChatFromViewer'))
self._send_message(ProxiedMessage('ChatFromViewer'))
self._send_message(ProxiedMessage('ChatFromViewer'))
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=2))
self._send_message(ProxiedMessage('ChatFromViewer'))
self._send_message(Message('ChatFromViewer', packet_id=1))
self._send_message(Message('ChatFromViewer'))
self._send_message(Message('ChatFromViewer'))
self._send_message(Message('ChatFromViewer'))
self._send_message(Message('ChatFromViewer', packet_id=2))
self._send_message(Message('ChatFromViewer'))
self.assertSequenceEqual(self.circuit.sent_simple, [
(1, "ChatFromViewer", Direction.OUT, False, ()),
@@ -119,14 +116,14 @@ class PacketIDTests(unittest.TestCase):
])
def test_packet_ack_field_converted(self):
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=1))
self._send_message(ProxiedMessage('ChatFromViewer'))
self._send_message(ProxiedMessage('ChatFromViewer'))
self._send_message(ProxiedMessage('ChatFromViewer', flags=PacketFlags.RELIABLE))
self._send_message(ProxiedMessage('ChatFromSimulator', packet_id=1, acks=(2, 3, 4)), outgoing=False)
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=2, flags=PacketFlags.RELIABLE))
self._send_message(ProxiedMessage('ChatFromSimulator', packet_id=2, acks=[5]), outgoing=False)
self._send_message(ProxiedMessage('ChatFromViewer'))
self._send_message(Message('ChatFromViewer', packet_id=1))
self._send_message(Message('ChatFromViewer'))
self._send_message(Message('ChatFromViewer'))
self._send_message(Message('ChatFromViewer', flags=PacketFlags.RELIABLE))
self._send_message(Message('ChatFromSimulator', packet_id=1, acks=(2, 3, 4)), outgoing=False)
self._send_message(Message('ChatFromViewer', packet_id=2, flags=PacketFlags.RELIABLE))
self._send_message(Message('ChatFromSimulator', packet_id=2, acks=[5]), outgoing=False)
self._send_message(Message('ChatFromViewer'))
self.assertSequenceEqual(self.circuit.sent_simple, [
(1, "ChatFromViewer", Direction.OUT, False, ()),
@@ -143,12 +140,12 @@ class PacketIDTests(unittest.TestCase):
])
def test_packet_ack_proxied_message_converted(self):
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=1))
self._send_message(ProxiedMessage('ChatFromViewer'))
self._send_message(ProxiedMessage('ChatFromViewer'))
self._send_message(ProxiedMessage('ChatFromViewer', flags=PacketFlags.RELIABLE))
self._send_message(Message('ChatFromViewer', packet_id=1))
self._send_message(Message('ChatFromViewer'))
self._send_message(Message('ChatFromViewer'))
self._send_message(Message('ChatFromViewer', flags=PacketFlags.RELIABLE))
self._send_message(
ProxiedMessage(
Message(
'PacketAck',
Block('Packets', ID=2),
Block('Packets', ID=3),
@@ -157,12 +154,12 @@ class PacketIDTests(unittest.TestCase):
),
outgoing=False
)
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=2, flags=PacketFlags.RELIABLE))
self._send_message(Message('ChatFromViewer', packet_id=2, flags=PacketFlags.RELIABLE))
self._send_message(
ProxiedMessage('PacketAck', Block('Packets', ID=5), packet_id=2),
Message('PacketAck', Block('Packets', ID=5), packet_id=2),
outgoing=False
)
self._send_message(ProxiedMessage('ChatFromViewer'))
self._send_message(Message('ChatFromViewer'))
self.assertSequenceEqual(self.circuit.sent_simple, [
(1, "ChatFromViewer", Direction.OUT, False, ()),
@@ -180,12 +177,12 @@ class PacketIDTests(unittest.TestCase):
self.assertEqual(self.circuit.sent_msgs[5]["Packets"][0]["ID"], 2)
def test_drop_proxied_message(self):
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=1))
self._send_message(Message('ChatFromViewer', packet_id=1))
self.circuit.drop_message(
ProxiedMessage('ChatFromViewer', packet_id=2, flags=PacketFlags.RELIABLE),
Message('ChatFromViewer', packet_id=2, flags=PacketFlags.RELIABLE),
Direction.OUT,
)
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=3))
self._send_message(Message('ChatFromViewer', packet_id=3))
self.assertSequenceEqual(self.circuit.sent_simple, [
(1, "ChatFromViewer", Direction.OUT, False, ()),
@@ -195,12 +192,12 @@ class PacketIDTests(unittest.TestCase):
self.assertEqual(self.circuit.sent_msgs[1]["Packets"][0]["ID"], 2)
def test_unreliable_proxied_message(self):
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=1))
self._send_message(Message('ChatFromViewer', packet_id=1))
self.circuit.drop_message(
ProxiedMessage('ChatFromViewer', packet_id=2),
Message('ChatFromViewer', packet_id=2),
Direction.OUT,
)
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=3))
self._send_message(Message('ChatFromViewer', packet_id=3))
self.assertSequenceEqual(self.circuit.sent_simple, [
(1, "ChatFromViewer", Direction.OUT, False, ()),
@@ -208,15 +205,15 @@ class PacketIDTests(unittest.TestCase):
])
def test_dropped_proxied_message_acks_sent(self):
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=1))
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=2))
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=3))
self._send_message(ProxiedMessage('ChatFromSimulator'), outgoing=False)
self._send_message(Message('ChatFromViewer', packet_id=1))
self._send_message(Message('ChatFromViewer', packet_id=2))
self._send_message(Message('ChatFromViewer', packet_id=3))
self._send_message(Message('ChatFromSimulator'), outgoing=False)
self.circuit.drop_message(
ProxiedMessage('ChatFromViewer', packet_id=4, acks=(4,)),
Message('ChatFromViewer', packet_id=4, acks=(4,)),
Direction.OUT,
)
self._send_message(ProxiedMessage('ChatFromViewer', packet_id=5))
self._send_message(Message('ChatFromViewer', packet_id=5))
self.assertSequenceEqual(self.circuit.sent_simple, [
(1, "ChatFromViewer", Direction.OUT, False, ()),
@@ -233,76 +230,20 @@ class PacketIDTests(unittest.TestCase):
self.assertEqual(self.circuit.sent_msgs[4]["Packets"][0]["ID"], 3)
def test_resending_or_dropping(self):
self.circuit.send_message(ProxiedMessage('ChatFromViewer', packet_id=1))
to_drop = ProxiedMessage('ChatFromViewer', packet_id=2, flags=PacketFlags.RELIABLE)
self.circuit.drop_message(to_drop, Direction.OUT)
self.circuit.send_message(Message('ChatFromViewer', packet_id=1))
to_drop = Message('ChatFromViewer', packet_id=2, flags=PacketFlags.RELIABLE)
self.circuit.drop_message(to_drop)
with self.assertRaises(RuntimeError):
# Re-dropping the same message should raise
self.circuit.drop_message(to_drop, Direction.OUT)
self.circuit.drop_message(to_drop)
# Clears finalized flag
to_drop.packet_id = None
self.circuit.send_message(to_drop, Direction.OUT)
self.circuit.send_message(to_drop)
with self.assertRaises(RuntimeError):
self.circuit.send_message(to_drop, Direction.OUT)
self.circuit.send_message(to_drop)
self.assertSequenceEqual(self.circuit.sent_simple, [
(1, "ChatFromViewer", Direction.OUT, False, ()),
(1, "PacketAck", Direction.IN, True, ()),
# ended up getting the same packet ID when injected
(2, "ChatFromViewer", Direction.OUT, True, ()),
])
class HumanReadableMessageTests(unittest.TestCase):
def test_basic(self):
val = """
OUT FooMessage
[SomeBlock]
# IGNORE ME
SomeFloat = 1.0
SomeStr = "baz"
SomeVec = <1,1,1>
[OtherBlock]
UUID = 1f4ffb55-022e-49fb-8c63-6f159aed9b24
"""
msg = ProxiedMessage.from_human_string(val)
self.assertEqual(msg.name, "FooMessage")
self.assertEqual(set(msg.blocks.keys()), {"SomeBlock", "OtherBlock"})
self.assertSequenceEqual(msg["SomeBlock"][0]["SomeVec"], (1.0, 1.0, 1.0))
self.assertEqual(msg["OtherBlock"][0]["UUID"], uuid.UUID("1f4ffb55-022e-49fb-8c63-6f159aed9b24"))
def test_eval_allowed(self):
val = """
OUT FooMessage
[SomeBlock]
evaled =$ 1+1
"""
msg = ProxiedMessage.from_human_string(val, safe=False)
self.assertEqual(msg["SomeBlock"][0]["evaled"], 2)
def test_eval_disallowed(self):
val = """
OUT FooMessage
[SomeBlock]
evaled =$ 1+1
"""
with self.assertRaises(ValueError):
ProxiedMessage.from_human_string(val)
class TestMessageSubfieldSerializers(unittest.TestCase):
def setUp(self):
self.chat_msg = ProxiedMessage(
'ChatFromViewer',
Block('AgentData',
AgentID=UUID('550e8400-e29b-41d4-a716-446655440000'),
SessionID=UUID('550e8400-e29b-41d4-a716-446655440000')),
Block('ChatData', Message="Chatting\n", Type=1, Channel=0))
def test_pretty_repr(self):
expected_repr = r"""ProxiedMessage('ChatFromViewer',
Block('AgentData', AgentID=UUID('550e8400-e29b-41d4-a716-446655440000'), SessionID=UUID('550e8400-e29b-41d4-a716-446655440000')),
Block('ChatData', Message='Chatting\n', Type_=ChatType.NORMAL, Channel=0), direction=Direction.OUT)"""
self.assertEqual(expected_repr, self.chat_msg.repr(pretty=True))

View File

@@ -6,14 +6,13 @@ from typing import *
from unittest import mock
from hippolyzer.lib.base.datatypes import *
from hippolyzer.lib.base.message.message import Block
from hippolyzer.lib.base.message.message import Block, Message as Message
from hippolyzer.lib.base.message.udpdeserializer import UDPMessageDeserializer
from hippolyzer.lib.base.message.udpserializer import UDPMessageSerializer
from hippolyzer.lib.base.objects import Object, normalize_object_update_compressed_data
from hippolyzer.lib.base.templates import ExtraParamType
from hippolyzer.lib.proxy.addons import AddonManager
from hippolyzer.lib.proxy.addon_utils import BaseAddon
from hippolyzer.lib.proxy.message import ProxiedMessage as Message
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.base.templates import PCode
from hippolyzer.lib.proxy.vocache import RegionViewerObjectCacheChain, RegionViewerObjectCache, ViewerObjectCacheEntry
@@ -69,7 +68,7 @@ class ObjectManagerTestMixin(BaseProxyTest):
self.mock_get_region_object_cache_chain.return_value = RegionViewerObjectCacheChain([])
self.object_manager = self.region.objects
self.serializer = UDPMessageSerializer()
self.deserializer = UDPMessageDeserializer(message_cls=Message)
self.deserializer = UDPMessageDeserializer()
self.object_addon = ObjectTrackingAddon()
AddonManager.init([], None, [self.object_addon])

View File

@@ -2,7 +2,7 @@ import unittest
import hippolyzer.lib.base.serialization as se
from hippolyzer.lib.base.datatypes import UUID
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.base.templates import TextureEntrySubfieldSerializer, TEFaceBitfield
EXAMPLE_TE = b"\x89UgG$\xcbC\xed\x92\x0bG\xca\xed\x15F_\x08\xe7\xb2\x98\x04\xca\x10;\x85\x94\x05Lj\x8d\xd4" \
@@ -44,7 +44,7 @@ class TemplateTests(unittest.TestCase):
'MediaFlags': {None: {'WebPage': False, 'TexGen': 'DEFAULT', '_Unused': 0}}, 'Glow': {None: 0},
'Materials': {None: '00000000-0000-0000-0000-000000000000'},
}
msg = ProxiedMessage.from_human_string(f"""
msg = Message.from_human_string(f"""
OUT ObjectImage
[AgentData]
AgentID = {UUID()}
@@ -55,7 +55,7 @@ class TemplateTests(unittest.TestCase):
TextureEntry =| {repr(pod_te)}
""")
# Make sure from/to_human_string doesn't change meaning
msg = ProxiedMessage.from_human_string(msg.to_human_string(beautify=True))
msg = Message.from_human_string(msg.to_human_string(beautify=True))
spec = msg["ObjectData"][0].get_serializer("TextureEntry")
deser = spec.deserialize(None, msg["ObjectData"]["TextureEntry"], pod=True)
self.assertEqual(deser, pod_te)

View File

@@ -4,7 +4,7 @@ import unittest
from typing import *
from hippolyzer.lib.base.datatypes import UUID
from hippolyzer.lib.base.message.message import Block
from hippolyzer.lib.base.message.message import Block, Message
from hippolyzer.lib.base.message.message_handler import MessageHandler
from hippolyzer.lib.base.templates import (
AssetType,
@@ -16,18 +16,17 @@ from hippolyzer.lib.base.templates import (
TransferStatus,
)
from hippolyzer.lib.proxy.circuit import ProxiedCircuit
from hippolyzer.lib.proxy.message import ProxiedMessage
from hippolyzer.lib.proxy.packets import Direction
from hippolyzer.lib.base.network.transport import Direction
from hippolyzer.lib.proxy.transfer_manager import TransferManager, Transfer
from hippolyzer.lib.proxy.xfer_manager import XferManager
class MockHandlingCircuit(ProxiedCircuit):
def __init__(self, handler: MessageHandler[ProxiedMessage]):
def __init__(self, handler: MessageHandler[Message]):
super().__init__(("127.0.0.1", 1), ("127.0.0.1", 2), None)
self.handler = handler
def _send_prepared_message(self, message: ProxiedMessage, direction, transport=None):
def _send_prepared_message(self, message: Message, transport=None):
asyncio.get_event_loop().call_soon(self.handler.handle, message)
@@ -36,8 +35,8 @@ class BaseTransferTests(unittest.IsolatedAsyncioTestCase):
LARGE_PAYLOAD = b"foobar" * 500
def setUp(self) -> None:
self.server_message_handler: MessageHandler[ProxiedMessage] = MessageHandler()
self.client_message_handler: MessageHandler[ProxiedMessage] = MessageHandler()
self.server_message_handler: MessageHandler[Message] = MessageHandler()
self.client_message_handler: MessageHandler[Message] = MessageHandler()
# The client side should send messages to the server side's message handler
# and vice-versa
self.client_circuit = MockHandlingCircuit(self.server_message_handler)
@@ -62,7 +61,7 @@ class XferManagerTests(BaseTransferTests):
manager = XferManager(self.server_message_handler, self.server_circuit)
xfer = await manager.request(vfile_id=asset_id, vfile_type=AssetType.BODYPART)
self.received_bytes = xfer.reassemble_chunks()
self.server_circuit.send_message(ProxiedMessage(
self.server_circuit.send_message(Message(
"AssetUploadComplete",
Block("AssetBlock", UUID=asset_id, Type=asset_block["Type"], Success=True),
direction=Direction.IN,
@@ -102,7 +101,7 @@ class TestTransferManager(BaseTransferTests):
self.assertEqual(EstateAssetType.COVENANT, params.EstateAssetType)
data = self.LARGE_PAYLOAD
self.server_circuit.send_message(ProxiedMessage(
self.server_circuit.send_message(Message(
'TransferInfo',
Block(
'TransferInfo',
@@ -118,7 +117,7 @@ class TestTransferManager(BaseTransferTests):
while True:
chunk = data[:1000]
data = data[1000:]
self.server_circuit.send_message(ProxiedMessage(
self.server_circuit.send_message(Message(
'TransferPacket',
Block(
'TransferData',