Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3149d3610f | ||
|
|
f8f3bcfc36 | ||
|
|
8548cce4e5 | ||
|
|
ad2aca1803 | ||
|
|
8cf500ce44 | ||
|
|
ceda7f370e | ||
|
|
0692a10253 | ||
|
|
c1c2a96295 | ||
|
|
b4be9fa757 | ||
|
|
a8967f0b7d | ||
|
|
10af5cc250 |
5
.github/workflows/bundle_windows.yml
vendored
5
.github/workflows/bundle_windows.yml
vendored
@@ -38,7 +38,8 @@ jobs:
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
pip install -e .
|
||||
pip install cx_freeze
|
||||
# Pinned for now until I figure out why it freaks out on bundling PySide6
|
||||
pip install cx_freeze==6.15.15
|
||||
|
||||
- name: Bundle with cx_Freeze
|
||||
shell: bash
|
||||
@@ -51,7 +52,7 @@ jobs:
|
||||
mv ./dist/*.zip hippolyzer-windows-${{ env.target_tag }}.zip
|
||||
|
||||
- name: Upload the artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: hippolyzer-windows-${{ env.sha }}
|
||||
path: ./hippolyzer-windows-${{ env.target_tag }}.zip
|
||||
|
||||
2
.github/workflows/pypi_publish.yml
vendored
2
.github/workflows/pypi_publish.yml
vendored
@@ -36,6 +36,7 @@ jobs:
|
||||
user: __token__
|
||||
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
|
||||
repository_url: https://test.pypi.org/legacy/
|
||||
attestations: false
|
||||
|
||||
- name: Publish to PyPI
|
||||
if: startsWith(github.event.ref, 'refs/tags') || github.event_name == 'release'
|
||||
@@ -43,3 +44,4 @@ jobs:
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
attestations: false
|
||||
|
||||
@@ -322,12 +322,12 @@ class JankStringyBytes(bytes):
|
||||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, bytes):
|
||||
return bytes(self) + other
|
||||
return JankStringyBytes(bytes(self) + other)
|
||||
return str(self) + other
|
||||
|
||||
def __radd__(self, other):
|
||||
if isinstance(other, bytes):
|
||||
return other + bytes(self)
|
||||
return JankStringyBytes(other + bytes(self))
|
||||
return other + str(self)
|
||||
|
||||
def lower(self):
|
||||
@@ -336,6 +336,20 @@ class JankStringyBytes(bytes):
|
||||
def upper(self):
|
||||
return str(self).upper()
|
||||
|
||||
def startswith(self, __prefix, __start=None, __end=None):
|
||||
if __start or __end:
|
||||
raise RuntimeError("Can't handle __start or __end")
|
||||
if isinstance(__prefix, str):
|
||||
return str(self).startswith(__prefix)
|
||||
return self.startswith(__prefix)
|
||||
|
||||
def endswith(self, __prefix, __start=None, __end=None):
|
||||
if __start or __end:
|
||||
raise RuntimeError("Can't handle __start or __end")
|
||||
if isinstance(__prefix, str):
|
||||
return str(self).endswith(__prefix)
|
||||
return self.endswith(__prefix)
|
||||
|
||||
|
||||
class RawBytes(bytes):
|
||||
__slots__ = ()
|
||||
|
||||
@@ -41,7 +41,8 @@ class Event:
|
||||
|
||||
return self
|
||||
|
||||
def _handler_key(self, handler):
|
||||
@staticmethod
|
||||
def _handler_key(handler):
|
||||
return handler[:3]
|
||||
|
||||
def unsubscribe(self, handler, *args, **kwargs):
|
||||
@@ -55,21 +56,23 @@ class Event:
|
||||
raise ValueError(f"Handler {handler!r} is not subscribed to this event.")
|
||||
return self
|
||||
|
||||
def _create_async_wrapper(self, handler, args, inner_args, kwargs):
|
||||
# Note that unsubscription may be delayed due to asyncio scheduling :)
|
||||
async def _run_handler_wrapper():
|
||||
unsubscribe = await handler(args, *inner_args, **kwargs)
|
||||
if unsubscribe:
|
||||
_ = self.unsubscribe(handler, *inner_args, **kwargs)
|
||||
return _run_handler_wrapper
|
||||
|
||||
def notify(self, args):
|
||||
for handler in self.subscribers[:]:
|
||||
handler, inner_args, kwargs, one_shot, predicate = handler
|
||||
for subscriber in self.subscribers[:]:
|
||||
handler, inner_args, kwargs, one_shot, predicate = subscriber
|
||||
if predicate and not predicate(args):
|
||||
continue
|
||||
if one_shot:
|
||||
self.unsubscribe(handler, *inner_args, **kwargs)
|
||||
if asyncio.iscoroutinefunction(handler):
|
||||
# Note that unsubscription may be delayed due to asyncio scheduling :)
|
||||
|
||||
async def _run_handler_wrapper():
|
||||
unsubscribe = await handler(args, *inner_args, **kwargs)
|
||||
if unsubscribe:
|
||||
_ = self.unsubscribe(handler, *inner_args, **kwargs)
|
||||
create_logged_task(_run_handler_wrapper(), self.name, LOG)
|
||||
create_logged_task(self._create_async_wrapper(handler, args, inner_args, kwargs)(), self.name, LOG)
|
||||
else:
|
||||
try:
|
||||
if handler(args, *inner_args, **kwargs) and not one_shot:
|
||||
|
||||
@@ -171,7 +171,7 @@ def get_mtime(path):
|
||||
|
||||
def fut_logger(name: str, logger: logging.Logger, fut: asyncio.Future, *args) -> None:
|
||||
"""Callback suitable for exception logging in `Future.add_done_callback()`"""
|
||||
if fut.exception():
|
||||
if not fut.cancelled() and fut.exception():
|
||||
if isinstance(fut.exception(), asyncio.CancelledError):
|
||||
# Don't really care if the task was just cancelled
|
||||
return
|
||||
|
||||
@@ -190,32 +190,36 @@ class SchemaBase(abc.ABC):
|
||||
def from_llsd(cls, inv_dict: Dict, flavor: str = "legacy"):
|
||||
fields = cls._get_fields_dict(llsd_flavor=flavor)
|
||||
obj_dict = {}
|
||||
for key, val in inv_dict.items():
|
||||
if key in fields:
|
||||
field: dataclasses.Field = fields[key]
|
||||
key = field.name
|
||||
spec = field.metadata.get("spec")
|
||||
# Not a real key, an internal var on our dataclass
|
||||
if not spec:
|
||||
LOG.warning(f"Internal key {key!r}")
|
||||
continue
|
||||
try:
|
||||
for key, val in inv_dict.items():
|
||||
if key in fields:
|
||||
field: dataclasses.Field = fields[key]
|
||||
key = field.name
|
||||
spec = field.metadata.get("spec")
|
||||
# Not a real key, an internal var on our dataclass
|
||||
if not spec:
|
||||
LOG.warning(f"Internal key {key!r}")
|
||||
continue
|
||||
|
||||
spec_cls = spec
|
||||
if not inspect.isclass(spec_cls):
|
||||
spec_cls = spec_cls.__class__
|
||||
spec_cls = spec
|
||||
if not inspect.isclass(spec_cls):
|
||||
spec_cls = spec_cls.__class__
|
||||
|
||||
# some kind of nested structure like sale_info
|
||||
if issubclass(spec_cls, SchemaBase):
|
||||
obj_dict[key] = spec.from_llsd(val, flavor)
|
||||
elif issubclass(spec_cls, SchemaFieldSerializer):
|
||||
obj_dict[key] = spec.from_llsd(val, flavor)
|
||||
# some kind of nested structure like sale_info
|
||||
if issubclass(spec_cls, SchemaBase):
|
||||
obj_dict[key] = spec.from_llsd(val, flavor)
|
||||
elif issubclass(spec_cls, SchemaFieldSerializer):
|
||||
obj_dict[key] = spec.from_llsd(val, flavor)
|
||||
else:
|
||||
raise ValueError(f"Unsupported spec for {key!r}, {spec!r}")
|
||||
else:
|
||||
raise ValueError(f"Unsupported spec for {key!r}, {spec!r}")
|
||||
else:
|
||||
if flavor != "ais":
|
||||
# AIS has a number of different fields that are irrelevant depending on
|
||||
# what exactly sent the payload
|
||||
LOG.warning(f"Unknown key {key!r}")
|
||||
if flavor != "ais":
|
||||
# AIS has a number of different fields that are irrelevant depending on
|
||||
# what exactly sent the payload
|
||||
LOG.warning(f"Unknown key {key!r}")
|
||||
except:
|
||||
LOG.error(f"Failed to parse inventory schema: {inv_dict!r}")
|
||||
raise
|
||||
return cls._obj_from_dict(obj_dict)
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
|
||||
@@ -16,10 +16,12 @@ from hippolyzer.lib.base.datatypes import *
|
||||
class HippoLLSDBaseFormatter(base_llsd.base.LLSDBaseFormatter):
|
||||
UUID: callable
|
||||
ARRAY: callable
|
||||
BINARY: callable
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.type_map[UUID] = self.UUID
|
||||
self.type_map[JankStringyBytes] = self.BINARY
|
||||
self.type_map[Vector2] = self.TUPLECOORD
|
||||
self.type_map[Vector3] = self.TUPLECOORD
|
||||
self.type_map[Vector4] = self.TUPLECOORD
|
||||
@@ -101,7 +103,7 @@ def _format_binary_recurse(something) -> bytes:
|
||||
raise LLSDSerializationError(str(exc), something)
|
||||
elif isinstance(something, uuid.UUID):
|
||||
return b'u' + something.bytes
|
||||
elif isinstance(something, binary):
|
||||
elif isinstance(something, (binary, JankStringyBytes)):
|
||||
return b'b' + struct.pack('!i', len(something)) + something
|
||||
elif is_string(something):
|
||||
if is_unicode(something):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -188,7 +188,7 @@ class MsgBlockList(List["Block"]):
|
||||
class Message:
|
||||
__slots__ = ("name", "send_flags", "packet_id", "acks", "body_boundaries", "queued",
|
||||
"offset", "raw_extra", "raw_body", "deserializer", "_blocks", "finalized",
|
||||
"direction", "meta", "synthetic", "dropped", "sender")
|
||||
"direction", "meta", "synthetic", "dropped", "sender", "unknown_message")
|
||||
|
||||
def __init__(self, name, *args, packet_id=None, flags=0, acks=None, direction=None):
|
||||
# TODO: Do this on a timer or something.
|
||||
@@ -200,6 +200,7 @@ class Message:
|
||||
|
||||
self.acks = acks if acks is not None else tuple()
|
||||
self.body_boundaries = (-1, -1)
|
||||
self.unknown_message = False
|
||||
self.offset = 0
|
||||
self.raw_extra = b""
|
||||
self.direction: Direction = direction if direction is not None else Direction.OUT
|
||||
@@ -288,7 +289,7 @@ class Message:
|
||||
|
||||
def ensure_parsed(self):
|
||||
# This is a little magic, think about whether we want this.
|
||||
if self.raw_body and self.deserializer():
|
||||
if self.raw_body and self.deserializer and self.deserializer():
|
||||
self.deserializer().parse_message_body(self)
|
||||
|
||||
def to_dict(self, extended=False):
|
||||
|
||||
@@ -126,8 +126,14 @@ class UDPMessageDeserializer:
|
||||
frequency, num = _parse_msg_num(reader)
|
||||
current_template = self.template_dict.get_template_by_pair(frequency, num)
|
||||
if current_template is None:
|
||||
raise exc.MessageTemplateNotFound("deserializing data", f"{frequency}:{num}")
|
||||
msg.name = current_template.name
|
||||
if self.settings.ALLOW_UNKNOWN_MESSAGES:
|
||||
LOG.warning(f"Unknown message type {frequency}:{num}")
|
||||
msg.unknown_message = True
|
||||
msg.name = "UnknownMessage:%d" % num
|
||||
else:
|
||||
raise exc.MessageTemplateNotFound("deserializing data", f"{frequency}:{num}")
|
||||
else:
|
||||
msg.name = current_template.name
|
||||
|
||||
# extra field, see note regarding msg.offset
|
||||
msg.raw_extra = reader.read_bytes(msg.offset)
|
||||
@@ -143,6 +149,12 @@ class UDPMessageDeserializer:
|
||||
# Already parsed if we don't have a raw body
|
||||
if not raw_body:
|
||||
return
|
||||
|
||||
if msg.unknown_message:
|
||||
# We can't parse this, we don't know anything about it
|
||||
msg.deserializer = None
|
||||
return
|
||||
|
||||
msg.raw_body = None
|
||||
msg.deserializer = None
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ class UDPMessageSerializer:
|
||||
|
||||
def serialize(self, msg: Message):
|
||||
current_template = self.template_dict.get_template_by_name(msg.name)
|
||||
if current_template is None:
|
||||
if current_template is None and msg.raw_body is None:
|
||||
raise exc.MessageSerializationError("message name", "invalid message name")
|
||||
|
||||
# Header and trailers are all big-endian
|
||||
|
||||
@@ -1728,7 +1728,6 @@ class QuantizedNumPyArray(Adapter):
|
||||
|
||||
def subfield_serializer(msg_name, block_name, var_name):
|
||||
def f(orig_cls):
|
||||
global SUBFIELD_SERIALIZERS
|
||||
SUBFIELD_SERIALIZERS[(msg_name, block_name, var_name)] = orig_cls
|
||||
return orig_cls
|
||||
return f
|
||||
@@ -1940,7 +1939,6 @@ class IntFlagSubfieldSerializer(AdapterInstanceSubfieldSerializer):
|
||||
|
||||
def http_serializer(msg_name):
|
||||
def f(orig_cls):
|
||||
global HTTP_SERIALIZERS
|
||||
HTTP_SERIALIZERS[msg_name] = orig_cls
|
||||
return orig_cls
|
||||
return f
|
||||
|
||||
@@ -55,6 +55,7 @@ class SettingDescriptor(Generic[_T]):
|
||||
|
||||
class Settings:
|
||||
ENABLE_DEFERRED_PACKET_PARSING: bool = SettingDescriptor(True)
|
||||
ALLOW_UNKNOWN_MESSAGES: bool = SettingDescriptor(True)
|
||||
|
||||
def __init__(self):
|
||||
self._settings: Dict[str, Any] = {}
|
||||
|
||||
@@ -1822,9 +1822,20 @@ class ChatSourceType(IntEnum):
|
||||
UNKNOWN = 3
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ThrottleData:
|
||||
resend: float = se.dataclass_field(se.F32)
|
||||
land: float = se.dataclass_field(se.F32)
|
||||
wind: float = se.dataclass_field(se.F32)
|
||||
cloud: float = se.dataclass_field(se.F32)
|
||||
task: float = se.dataclass_field(se.F32)
|
||||
texture: float = se.dataclass_field(se.F32)
|
||||
asset: float = se.dataclass_field(se.F32)
|
||||
|
||||
|
||||
@se.subfield_serializer("AgentThrottle", "Throttle", "Throttles")
|
||||
class AgentThrottlesSerializer(se.SimpleSubfieldSerializer):
|
||||
TEMPLATE = se.Collection(None, se.F32)
|
||||
TEMPLATE = se.Dataclass(ThrottleData)
|
||||
|
||||
|
||||
@se.subfield_serializer("ObjectUpdate", "ObjectData", "NameValue")
|
||||
|
||||
@@ -5,6 +5,7 @@ Body parts and linden clothing layers
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import enum
|
||||
import logging
|
||||
from io import StringIO
|
||||
from typing import *
|
||||
@@ -21,6 +22,60 @@ LOG = logging.getLogger(__name__)
|
||||
_T = TypeVar("_T")
|
||||
|
||||
WEARABLE_VERSION = "LLWearable version 22"
|
||||
DEFAULT_WEARABLE_TEX = UUID("c228d1cf-4b5d-4ba8-84f4-899a0796aa97")
|
||||
|
||||
|
||||
class AvatarTEIndex(enum.IntEnum):
|
||||
"""From llavatarappearancedefines.h"""
|
||||
HEAD_BODYPAINT = 0
|
||||
UPPER_SHIRT = enum.auto()
|
||||
LOWER_PANTS = enum.auto()
|
||||
EYES_IRIS = enum.auto()
|
||||
HAIR = enum.auto()
|
||||
UPPER_BODYPAINT = enum.auto()
|
||||
LOWER_BODYPAINT = enum.auto()
|
||||
LOWER_SHOES = enum.auto()
|
||||
HEAD_BAKED = enum.auto()
|
||||
UPPER_BAKED = enum.auto()
|
||||
LOWER_BAKED = enum.auto()
|
||||
EYES_BAKED = enum.auto()
|
||||
LOWER_SOCKS = enum.auto()
|
||||
UPPER_JACKET = enum.auto()
|
||||
LOWER_JACKET = enum.auto()
|
||||
UPPER_GLOVES = enum.auto()
|
||||
UPPER_UNDERSHIRT = enum.auto()
|
||||
LOWER_UNDERPANTS = enum.auto()
|
||||
SKIRT = enum.auto()
|
||||
SKIRT_BAKED = enum.auto()
|
||||
HAIR_BAKED = enum.auto()
|
||||
LOWER_ALPHA = enum.auto()
|
||||
UPPER_ALPHA = enum.auto()
|
||||
HEAD_ALPHA = enum.auto()
|
||||
EYES_ALPHA = enum.auto()
|
||||
HAIR_ALPHA = enum.auto()
|
||||
HEAD_TATTOO = enum.auto()
|
||||
UPPER_TATTOO = enum.auto()
|
||||
LOWER_TATTOO = enum.auto()
|
||||
HEAD_UNIVERSAL_TATTOO = enum.auto()
|
||||
UPPER_UNIVERSAL_TATTOO = enum.auto()
|
||||
LOWER_UNIVERSAL_TATTOO = enum.auto()
|
||||
SKIRT_TATTOO = enum.auto()
|
||||
HAIR_TATTOO = enum.auto()
|
||||
EYES_TATTOO = enum.auto()
|
||||
LEFT_ARM_TATTOO = enum.auto()
|
||||
LEFT_LEG_TATTOO = enum.auto()
|
||||
AUX1_TATTOO = enum.auto()
|
||||
AUX2_TATTOO = enum.auto()
|
||||
AUX3_TATTOO = enum.auto()
|
||||
LEFTARM_BAKED = enum.auto()
|
||||
LEFTLEG_BAKED = enum.auto()
|
||||
AUX1_BAKED = enum.auto()
|
||||
AUX2_BAKED = enum.auto()
|
||||
AUX3_BAKED = enum.auto()
|
||||
|
||||
@property
|
||||
def is_baked(self) -> bool:
|
||||
return self.name.endswith("_BAKED")
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
|
||||
@@ -23,7 +23,7 @@ from hippolyzer.lib.base.message.udpdeserializer import UDPMessageDeserializer
|
||||
from hippolyzer.lib.base.network.caps_client import CapsClient, CAPS_DICT
|
||||
from hippolyzer.lib.base.network.transport import ADDR_TUPLE, Direction, SocketUDPTransport, AbstractUDPTransport
|
||||
from hippolyzer.lib.base.settings import Settings, SettingDescriptor
|
||||
from hippolyzer.lib.base.templates import RegionHandshakeReplyFlags, ChatType
|
||||
from hippolyzer.lib.base.templates import RegionHandshakeReplyFlags, ChatType, ThrottleData
|
||||
from hippolyzer.lib.base.transfer_manager import TransferManager
|
||||
from hippolyzer.lib.base.xfer_manager import XferManager
|
||||
from hippolyzer.lib.client.asset_uploader import AssetUploader
|
||||
@@ -108,8 +108,9 @@ class HippoClientProtocol(asyncio.DatagramProtocol):
|
||||
if should_handle:
|
||||
self.session.message_handler.handle(message)
|
||||
except:
|
||||
LOG.exception("Failed in region message handler")
|
||||
region.message_handler.handle(message)
|
||||
LOG.exception("Failed in session message handler")
|
||||
if should_handle:
|
||||
region.message_handler.handle(message)
|
||||
|
||||
|
||||
class HippoClientRegion(BaseClientRegion):
|
||||
@@ -189,7 +190,7 @@ class HippoClientRegion(BaseClientRegion):
|
||||
"RegionInfo",
|
||||
Flags=(
|
||||
RegionHandshakeReplyFlags.SUPPORTS_SELF_APPEARANCE
|
||||
| RegionHandshakeReplyFlags.VOCACHE_IS_EMPTY
|
||||
| RegionHandshakeReplyFlags.VOCACHE_CULLING_ENABLED
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -207,7 +208,15 @@ class HippoClientRegion(BaseClientRegion):
|
||||
"Throttle",
|
||||
GenCounter=0,
|
||||
# Reasonable defaults, I guess
|
||||
Throttles_=[207360.0, 165376.0, 33075.19921875, 33075.19921875, 682700.75, 682700.75, 269312.0],
|
||||
Throttles_=ThrottleData(
|
||||
resend=207360.0,
|
||||
land=165376.0,
|
||||
wind=33075.19921875,
|
||||
cloud=33075.19921875,
|
||||
task=682700.75,
|
||||
texture=682700.75,
|
||||
asset=269312.0
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -276,21 +285,25 @@ class HippoClientRegion(BaseClientRegion):
|
||||
ack: Optional[int] = None
|
||||
while True:
|
||||
payload = {"ack": ack, "done": False}
|
||||
async with self.caps_client.post("EventQueueGet", llsd=payload) as resp:
|
||||
if resp.status != 200:
|
||||
await asyncio.sleep(0.1)
|
||||
continue
|
||||
polled = await resp.read_llsd()
|
||||
for event in polled["events"]:
|
||||
if self._llsd_serializer.can_handle(event["message"]):
|
||||
msg = self._llsd_serializer.deserialize(event)
|
||||
else:
|
||||
msg = Message.from_eq_event(event)
|
||||
msg.sender = self.circuit_addr
|
||||
msg.direction = Direction.IN
|
||||
self.session().message_handler.handle(msg)
|
||||
self.message_handler.handle(msg)
|
||||
ack = polled["id"]
|
||||
try:
|
||||
async with self.caps_client.post("EventQueueGet", llsd=payload) as resp:
|
||||
if resp.status != 200:
|
||||
await asyncio.sleep(0.1)
|
||||
continue
|
||||
polled = await resp.read_llsd()
|
||||
for event in polled["events"]:
|
||||
if self._llsd_serializer.can_handle(event["message"]):
|
||||
msg = self._llsd_serializer.deserialize(event)
|
||||
else:
|
||||
msg = Message.from_eq_event(event)
|
||||
msg.sender = self.circuit_addr
|
||||
msg.direction = Direction.IN
|
||||
self.session().message_handler.handle(msg)
|
||||
self.message_handler.handle(msg)
|
||||
ack = polled["id"]
|
||||
await asyncio.sleep(0.001)
|
||||
except aiohttp.client_exceptions.ServerDisconnectedError:
|
||||
# This is expected to happen during long-polling, just pick up again where we left off.
|
||||
await asyncio.sleep(0.001)
|
||||
|
||||
async def _handle_ping_check(self, message: Message):
|
||||
|
||||
@@ -116,18 +116,6 @@ class IPCInterceptionAddon:
|
||||
self.to_proxy_queue: multiprocessing.Queue = flow_context.to_proxy_queue
|
||||
self.shutdown_signal: multiprocessing.Event = flow_context.shutdown_signal
|
||||
|
||||
def add_log(self, entry: mitmproxy.log.LogEntry):
|
||||
if entry.level == "debug":
|
||||
logging.debug(entry.msg)
|
||||
elif entry.level in ("alert", "info"):
|
||||
# TODO: All mitmproxy infos are basically debugs, should
|
||||
# probably give these dedicated loggers
|
||||
logging.debug(entry.msg)
|
||||
elif entry.level == "warn":
|
||||
logging.warning(entry.msg)
|
||||
elif entry.level == "error":
|
||||
logging.error(entry.msg)
|
||||
|
||||
def running(self):
|
||||
# register to pump the events or something here
|
||||
create_logged_task(self._pump_callbacks(), "Pump HTTP proxy callbacks")
|
||||
|
||||
6
setup.py
6
setup.py
@@ -100,9 +100,9 @@ setup(
|
||||
# Proxy-specific stuff
|
||||
'outleap<1.0',
|
||||
'arpeggio',
|
||||
# 11.x will be a major change.
|
||||
'mitmproxy>=10.0.0,<11',
|
||||
'Werkzeug<3.0',
|
||||
# 12.x will be a major change.
|
||||
'mitmproxy>=11.0.0,<12',
|
||||
'Werkzeug<4.0',
|
||||
# For REPLs
|
||||
'ptpython<4.0',
|
||||
# These could be in extras_require if you don't want a GUI.
|
||||
|
||||
@@ -49,3 +49,15 @@ class TestEvents(unittest.IsolatedAsyncioTestCase):
|
||||
await called.wait()
|
||||
mock.assert_called_with("foo")
|
||||
self.assertNotIn(_mock_wrapper, [x[0] for x in self.event.subscribers])
|
||||
|
||||
async def test_multiple_subscribers(self):
|
||||
called = asyncio.Event()
|
||||
called2 = asyncio.Event()
|
||||
|
||||
self.event.subscribe(lambda *args: called.set())
|
||||
self.event.subscribe(lambda *args: called2.set())
|
||||
|
||||
self.event.notify(None)
|
||||
|
||||
self.assertTrue(called.is_set())
|
||||
self.assertTrue(called2.is_set())
|
||||
|
||||
@@ -181,6 +181,8 @@ class TestMessageHandlers(unittest.IsolatedAsyncioTestCase):
|
||||
self.message_handler.handle(msg)
|
||||
|
||||
async def test_subscription(self):
|
||||
called = asyncio.Event()
|
||||
called2 = asyncio.Event()
|
||||
with self.message_handler.subscribe_async(
|
||||
message_names=("Foo",),
|
||||
predicate=lambda m: m["Bar"]["Baz"] == 1,
|
||||
@@ -192,6 +194,10 @@ class TestMessageHandlers(unittest.IsolatedAsyncioTestCase):
|
||||
msg3 = Message("Foo", Block("Bar", Baz=1, Biz=3))
|
||||
self._fake_received_message(msg1)
|
||||
self._fake_received_message(msg2)
|
||||
|
||||
self.message_handler.subscribe("Foo", lambda *args: called.set())
|
||||
self.message_handler.subscribe("Foo", lambda *args: called2.set())
|
||||
|
||||
self._fake_received_message(msg3)
|
||||
received = []
|
||||
while True:
|
||||
@@ -199,14 +205,15 @@ class TestMessageHandlers(unittest.IsolatedAsyncioTestCase):
|
||||
received.append(await asyncio.wait_for(get_msg(), 0.001))
|
||||
except asyncio.exceptions.TimeoutError:
|
||||
break
|
||||
self.assertEqual(len(foo_handlers), 1)
|
||||
self.assertEqual(len(foo_handlers), 3)
|
||||
self.assertListEqual(received, [msg1, msg3])
|
||||
# The message should have been take()n, making a copy
|
||||
self.assertIsNot(msg1, received[0])
|
||||
# take() was called, so this should have been marked queued
|
||||
self.assertTrue(msg1.queued)
|
||||
# Leaving the block should have unsubscribed automatically
|
||||
self.assertEqual(len(foo_handlers), 0)
|
||||
self.assertEqual(len(foo_handlers), 2)
|
||||
self.assertTrue(called.is_set())
|
||||
|
||||
async def test_subscription_no_take(self):
|
||||
with self.message_handler.subscribe_async(("Foo",), take=False) as get_msg:
|
||||
|
||||
@@ -50,6 +50,8 @@ OBJECT_UPDATE = binascii.unhexlify(''.join(OBJECT_UPDATE.split()))
|
||||
|
||||
COARSE_LOCATION_UPDATE = b'\x00\x00\x00\x00E\x00\xff\x06\x00\xff\xff\xff\xff\x00'
|
||||
|
||||
UNKNOWN_PACKET = b'\x00\x00\x00\x00E\x00\xff\xf0\x00\xff\xff\xff\xff\x00'
|
||||
|
||||
|
||||
class TestPacketDecode(unittest.TestCase):
|
||||
|
||||
@@ -110,3 +112,12 @@ class TestPacketDecode(unittest.TestCase):
|
||||
parsed = deserializer.deserialize(message)
|
||||
logging.debug("Parsed blocks: %r " % (list(parsed.blocks.keys()),))
|
||||
self.assertEqual(message, serializer.serialize(parsed))
|
||||
|
||||
def test_unknown_packet_roundtrips(self):
|
||||
message = UNKNOWN_PACKET
|
||||
deserializer = UDPMessageDeserializer(settings=self.settings)
|
||||
serializer = UDPMessageSerializer()
|
||||
parsed = deserializer.deserialize(message)
|
||||
logging.debug("Parsed blocks: %r " % (list(parsed.blocks.keys()),))
|
||||
self.assertEqual("UnknownMessage:240", parsed.name)
|
||||
self.assertEqual(message, serializer.serialize(parsed))
|
||||
|
||||
@@ -21,6 +21,9 @@ from hippolyzer.lib.proxy.sessions import Session
|
||||
from hippolyzer.lib.proxy.test_utils import BaseProxyTest
|
||||
|
||||
|
||||
UNKNOWN_PACKET = b'\x00\x00\x00\x00E\x00\xff\xf0\x00\xff\xff\xff\xff\x00'
|
||||
|
||||
|
||||
class MockAddon(BaseAddon):
|
||||
def __init__(self):
|
||||
self.events = []
|
||||
@@ -242,6 +245,21 @@ class LLUDPIntegrationTests(BaseProxyTest):
|
||||
self.assertEqual(entry.name, "UndoLand")
|
||||
self.assertEqual(entry.message.dropped, True)
|
||||
|
||||
async def test_logging_unknown_message(self):
|
||||
message_logger = SimpleMessageLogger()
|
||||
self.session_manager.message_logger = message_logger
|
||||
self._setup_default_circuit()
|
||||
self.protocol.datagram_received(UNKNOWN_PACKET, self.region_addr)
|
||||
await self._wait_drained()
|
||||
entries = message_logger.entries
|
||||
self.assertEqual(len(entries), 1)
|
||||
entry: LLUDPMessageLogEntry = entries[0] # type: ignore
|
||||
# Freezing shouldn't affect this
|
||||
entry.freeze()
|
||||
self.assertEqual(entry.name, "UnknownMessage:240")
|
||||
self.assertEqual(entry.message.dropped, False)
|
||||
self.assertEqual(entry.message.unknown_message, True)
|
||||
|
||||
async def test_session_message_handler(self):
|
||||
self._setup_default_circuit()
|
||||
obj_update = self._make_objectupdate_compressed(1234)
|
||||
|
||||
Reference in New Issue
Block a user