Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e7f887970 | ||
|
|
ef9df6b058 | ||
|
|
baae0f6d6e | ||
|
|
0f369b682d | ||
|
|
1f1e4de254 | ||
|
|
75ddc0a5ba | ||
|
|
e4cb168138 | ||
|
|
63aebba754 | ||
|
|
8cf1a43d59 | ||
|
|
bbc8813b61 | ||
|
|
5b51dbd30f | ||
|
|
295c7972e7 | ||
|
|
b034661c38 | ||
|
|
f12fd95ee1 |
16
.github/workflows/bundle_windows.yml
vendored
16
.github/workflows/bundle_windows.yml
vendored
@@ -13,7 +13,9 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: windows-latest
|
||||
runs-on: windows-2019
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.9]
|
||||
@@ -34,14 +36,22 @@ jobs:
|
||||
pip install cx_freeze
|
||||
|
||||
- name: Bundle with cx_Freeze
|
||||
shell: bash
|
||||
run: |
|
||||
python setup_cxfreeze.py build_exe
|
||||
pip install pip-licenses
|
||||
pip-licenses --format=plain-vertical --with-license-file --no-license-path --output-file=lib_licenses.txt
|
||||
python setup_cxfreeze.py finalize_cxfreeze
|
||||
# Should only be one, but we don't know what it's named
|
||||
mv ./dist/*.zip hippolyzer-windows-${{ github.ref }}.zip
|
||||
|
||||
- name: Upload the artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: hippolyzer-gui-windows-${{ github.sha }}
|
||||
path: ./dist/**
|
||||
name: hippolyzer-windows-${{ github.ref }}
|
||||
path: ./hippolyzer-windows-${{ github.ref }}.zip
|
||||
|
||||
- uses: ncipollo/release-action@v1.10.0
|
||||
with:
|
||||
artifacts: hippolyzer-windows-${{ github.ref }}.zip
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
22
README.md
22
README.md
@@ -83,6 +83,28 @@ SOCKS 5 works correctly on these platforms, so you can just configure it through
|
||||
the `no_proxy` env var appropriately. For ex. `no_proxy="asset-cdn.glb.agni.lindenlab.com" ./firestorm`.
|
||||
* Log in!
|
||||
|
||||
##### Firestorm
|
||||
|
||||
The proxy selection dialog in the most recent Firestorm release is non-functional, as
|
||||
https://bitbucket.org/lindenlab/viewer/commits/454c7f4543688126b2fa5c0560710f5a1733702e was not pulled in.
|
||||
|
||||
As a workaround, you can go to `Debug -> Show Debug Settings` and enter the following values:
|
||||
|
||||
| Name | Value |
|
||||
|---------------------|-----------|
|
||||
| HttpProxyType | Web |
|
||||
| BrowserProxyAddress | 127.0.0.1 |
|
||||
| BrowserProxyEnabled | TRUE |
|
||||
| BrowserProxyPort | 9062 |
|
||||
| Socks5ProxyEnabled | TRUE |
|
||||
| Socks5ProxyHost | 127.0.0.1 |
|
||||
| Socks5ProxyPort | 9061 |
|
||||
|
||||
Or, if you're on Linux, you can also use [LinHippoAutoProxy](https://github.com/SaladDais/LinHippoAutoProxy).
|
||||
|
||||
Connections from the in-viewer browser will likely _not_ be run through Hippolyzer when using either of
|
||||
these workarounds.
|
||||
|
||||
### Filtering
|
||||
|
||||
By default, the proxy's display filter is configured to ignore many high-frequency messages.
|
||||
|
||||
111
addon_examples/object_management_validator.py
Normal file
111
addon_examples/object_management_validator.py
Normal file
@@ -0,0 +1,111 @@
|
||||
"""
|
||||
Check object manager state against region ViewerObject cache
|
||||
|
||||
Can't look at every object we've tracked and every object in VOCache
|
||||
and report mismatches due to weird VOCache cache eviction criteria and certain
|
||||
cacheable objects not being added to the VOCache.
|
||||
|
||||
Off the top of my head, animesh objects get explicit KillObjects at extreme
|
||||
view distances same as avatars, but will still be present in the cache even
|
||||
though they will not be in gObjectList.
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import *
|
||||
|
||||
from hippolyzer.lib.base.objects import normalize_object_update_compressed_data
|
||||
from hippolyzer.lib.base.templates import ObjectUpdateFlags, PCode
|
||||
from hippolyzer.lib.proxy.addon_utils import BaseAddon, GlobalProperty
|
||||
from hippolyzer.lib.base.message.message import Message
|
||||
from hippolyzer.lib.proxy.addons import AddonManager
|
||||
from hippolyzer.lib.proxy.region import ProxiedRegion
|
||||
from hippolyzer.lib.proxy.sessions import SessionManager, Session
|
||||
from hippolyzer.lib.proxy.vocache import is_valid_vocache_dir, RegionViewerObjectCacheChain
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ObjectManagementValidator(BaseAddon):
|
||||
base_cache_path: Optional[str] = GlobalProperty(None)
|
||||
orig_auto_request: Optional[bool] = GlobalProperty(None)
|
||||
|
||||
def handle_init(self, session_manager: SessionManager):
|
||||
if self.orig_auto_request is None:
|
||||
self.orig_auto_request = session_manager.settings.ALLOW_AUTO_REQUEST_OBJECTS
|
||||
session_manager.settings.ALLOW_AUTO_REQUEST_OBJECTS = False
|
||||
|
||||
async def _choose_cache_path():
|
||||
while not self.base_cache_path:
|
||||
cache_dir = await AddonManager.UI.open_dir("Choose the base cache directory")
|
||||
if not cache_dir:
|
||||
return
|
||||
if not is_valid_vocache_dir(cache_dir):
|
||||
continue
|
||||
self.base_cache_path = cache_dir
|
||||
|
||||
if not self.base_cache_path:
|
||||
self._schedule_task(_choose_cache_path(), session_scoped=False)
|
||||
|
||||
def handle_unload(self, session_manager: SessionManager):
|
||||
session_manager.settings.ALLOW_AUTO_REQUEST_OBJECTS = self.orig_auto_request
|
||||
|
||||
def handle_session_init(self, session: Session):
|
||||
# Use only the specified cache path for the vocache
|
||||
session.cache_dir = self.base_cache_path
|
||||
|
||||
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: Message):
|
||||
if message.name != "DisableSimulator":
|
||||
return
|
||||
# Send it off to the client without handling it normally,
|
||||
# we need to defer region teardown in the proxy
|
||||
region.circuit.send(message)
|
||||
self._schedule_task(self._check_cache_before_region_teardown(region))
|
||||
return True
|
||||
|
||||
async def _check_cache_before_region_teardown(self, region: ProxiedRegion):
|
||||
await asyncio.sleep(0.5)
|
||||
print("Ok, checking cache differences")
|
||||
try:
|
||||
# Index will have been rewritten, so re-read it.
|
||||
region_cache_chain = RegionViewerObjectCacheChain.for_region(
|
||||
handle=region.handle,
|
||||
cache_id=region.cache_id,
|
||||
cache_dir=self.base_cache_path
|
||||
)
|
||||
if not region_cache_chain.region_caches:
|
||||
print(f"no caches for {region!r}?")
|
||||
return
|
||||
all_full_ids = set()
|
||||
for obj in region.objects.all_objects:
|
||||
cacheable = True
|
||||
orig_obj = obj
|
||||
# Walk along the ancestry checking for things that would make the tree non-cacheable
|
||||
while obj is not None:
|
||||
if obj.UpdateFlags & ObjectUpdateFlags.TEMPORARY_ON_REZ:
|
||||
cacheable = False
|
||||
if obj.PCode == PCode.AVATAR:
|
||||
cacheable = False
|
||||
obj = obj.Parent
|
||||
if cacheable:
|
||||
all_full_ids.add(orig_obj.FullID)
|
||||
|
||||
for key in all_full_ids:
|
||||
obj = region.objects.lookup_fullid(key)
|
||||
cached_data = region_cache_chain.lookup_object_data(obj.LocalID, obj.CRC)
|
||||
if not cached_data:
|
||||
continue
|
||||
orig_dict = obj.to_dict()
|
||||
parsed_data = normalize_object_update_compressed_data(cached_data)
|
||||
updated = obj.update_properties(parsed_data)
|
||||
# Can't compare this yet
|
||||
updated -= {"TextureEntry"}
|
||||
if updated:
|
||||
print(key)
|
||||
for attr in updated:
|
||||
print("\t", attr, orig_dict[attr], parsed_data[attr])
|
||||
finally:
|
||||
# Ok to teardown region in the proxy now
|
||||
region.mark_dead()
|
||||
|
||||
|
||||
addons = [ObjectManagementValidator()]
|
||||
@@ -87,10 +87,13 @@ class REPLAddon(BaseAddon):
|
||||
def run_http_proxy_process(proxy_host, http_proxy_port, flow_context: HTTPFlowContext):
|
||||
mitm_loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(mitm_loop)
|
||||
mitmproxy_master = create_http_proxy(proxy_host, http_proxy_port, flow_context)
|
||||
mitmproxy_master.start_server()
|
||||
gc.freeze()
|
||||
mitm_loop.run_forever()
|
||||
|
||||
async def mitmproxy_loop():
|
||||
mitmproxy_master = create_http_proxy(proxy_host, http_proxy_port, flow_context)
|
||||
gc.freeze()
|
||||
await mitmproxy_master.run()
|
||||
|
||||
asyncio.run(mitmproxy_loop())
|
||||
|
||||
|
||||
def start_proxy(session_manager: SessionManager, extra_addons: Optional[list] = None,
|
||||
@@ -105,7 +108,7 @@ def start_proxy(session_manager: SessionManager, extra_addons: Optional[list] =
|
||||
root_log.setLevel(logging.INFO)
|
||||
logging.basicConfig()
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop = asyncio.get_event_loop_policy().get_event_loop()
|
||||
|
||||
udp_proxy_port = session_manager.settings.SOCKS_PROXY_PORT
|
||||
http_proxy_port = session_manager.settings.HTTP_PROXY_PORT
|
||||
|
||||
@@ -58,6 +58,9 @@ class TupleCoord(recordclass.datatuple, _IterableStub): # type: ignore
|
||||
def __abs__(self):
|
||||
return self.__class__(*(abs(x) for x in self))
|
||||
|
||||
def __neg__(self):
|
||||
return self.__class__(*(-x for x in self))
|
||||
|
||||
def __add__(self, other):
|
||||
return self.__class__(*(x + y for x, y in zip(self, other)))
|
||||
|
||||
|
||||
@@ -107,7 +107,8 @@ class MessageHandler(Generic[_T, _K]):
|
||||
take = self.take_by_default
|
||||
notifiers = [self.register(name) for name in message_names]
|
||||
|
||||
fut = asyncio.get_event_loop().create_future()
|
||||
loop = asyncio.get_event_loop_policy().get_event_loop()
|
||||
fut = loop.create_future()
|
||||
timeout_task = None
|
||||
|
||||
async def _canceller():
|
||||
|
||||
@@ -270,6 +270,9 @@ def normalize_object_update_compressed_data(data: bytes):
|
||||
# Only used for determining which sections are present
|
||||
del compressed["Flags"]
|
||||
|
||||
# Unlike other ObjectUpdate types, a null value in an ObjectUpdateCompressed
|
||||
# always means that there is no value, not that the value hasn't changed
|
||||
# from the client's view. Use the default value when that happens.
|
||||
ps_block = compressed.pop("PSBlockNew", None)
|
||||
if ps_block is None:
|
||||
ps_block = compressed.pop("PSBlock", None)
|
||||
@@ -278,6 +281,20 @@ def normalize_object_update_compressed_data(data: bytes):
|
||||
compressed.pop("PSBlock", None)
|
||||
if compressed["NameValue"] is None:
|
||||
compressed["NameValue"] = NameValueCollection()
|
||||
if compressed["Text"] is None:
|
||||
compressed["Text"] = b""
|
||||
compressed["TextColor"] = b""
|
||||
if compressed["MediaURL"] is None:
|
||||
compressed["MediaURL"] = b""
|
||||
if compressed["AngularVelocity"] is None:
|
||||
compressed["AngularVelocity"] = Vector3()
|
||||
if compressed["SoundFlags"] is None:
|
||||
compressed["SoundFlags"] = 0
|
||||
compressed["SoundGain"] = 0.0
|
||||
compressed["SoundRadius"] = 0.0
|
||||
compressed["Sound"] = UUID()
|
||||
if compressed["TextureEntry"] is None:
|
||||
compressed["TextureEntry"] = tmpls.TextureEntry()
|
||||
|
||||
object_data = {
|
||||
"PSBlock": ps_block.value,
|
||||
@@ -286,9 +303,9 @@ def normalize_object_update_compressed_data(data: bytes):
|
||||
"LocalID": compressed.pop("ID"),
|
||||
**compressed,
|
||||
}
|
||||
if object_data["TextureEntry"] is None:
|
||||
object_data.pop("TextureEntry")
|
||||
# Don't clobber OwnerID in case the object has a proper one.
|
||||
# Don't clobber OwnerID in case the object has a proper one from
|
||||
# a previous ObjectProperties. OwnerID isn't expected to be populated
|
||||
# on ObjectUpdates unless an attached sound is playing.
|
||||
if object_data["OwnerID"] == UUID():
|
||||
del object_data["OwnerID"]
|
||||
return object_data
|
||||
|
||||
@@ -282,8 +282,8 @@ class AddonManager:
|
||||
|
||||
# Make sure module initialization happens after any pending task cancellations
|
||||
# due to module unloading.
|
||||
|
||||
asyncio.get_event_loop().call_soon(cls._init_module, mod)
|
||||
loop = asyncio.get_event_loop_policy().get_event_loop()
|
||||
loop.call_soon(cls._init_module, mod)
|
||||
except Exception as e:
|
||||
if had_mod:
|
||||
logging.exception("Exploded trying to reload addon %s" % spec.name)
|
||||
|
||||
@@ -70,7 +70,7 @@ class SLTlsConfig(mitmproxy.addons.tlsconfig.TlsConfig):
|
||||
)
|
||||
self.certstore.certs = old_cert_store.certs
|
||||
|
||||
def tls_start_server(self, tls_start: tls.TlsStartData):
|
||||
def tls_start_server(self, tls_start: tls.TlsData):
|
||||
super().tls_start_server(tls_start)
|
||||
# Since 2000 the recommendation per RFCs has been to only check SANs and not the CN field.
|
||||
# Most browsers do this, as does mitmproxy. The viewer does not, and the sim certs have no SAN
|
||||
@@ -229,10 +229,6 @@ class SLMITMMaster(mitmproxy.master.Master):
|
||||
SLMITMAddon(flow_context),
|
||||
)
|
||||
|
||||
def start_server(self):
|
||||
self.start()
|
||||
asyncio.ensure_future(self.running())
|
||||
|
||||
|
||||
def create_proxy_master(host, port, flow_context: HTTPFlowContext): # pragma: no cover
|
||||
opts = mitmproxy.options.Options()
|
||||
|
||||
@@ -36,7 +36,8 @@ class InterceptingLLUDPProxyProtocol(UDPProxyProtocol):
|
||||
)
|
||||
self.message_xml = MessageDotXML()
|
||||
self.session: Optional[Session] = None
|
||||
self.resend_task = asyncio.get_event_loop().create_task(self.attempt_resends())
|
||||
loop = asyncio.get_event_loop_policy().get_event_loop()
|
||||
self.resend_task = loop.create_task(self.attempt_resends())
|
||||
|
||||
async def attempt_resends(self):
|
||||
while True:
|
||||
|
||||
@@ -63,18 +63,25 @@ class ProxyObjectManager(ClientObjectManager):
|
||||
cache_dir=self._region.session().cache_dir,
|
||||
)
|
||||
|
||||
def request_missed_cached_objects_soon(self):
|
||||
def request_missed_cached_objects_soon(self, report_only=False):
|
||||
if self._cache_miss_timer:
|
||||
self._cache_miss_timer.cancel()
|
||||
# Basically debounce. Will only trigger 0.2 seconds after the last time it's invoked to
|
||||
# deal with the initial flood of ObjectUpdateCached and the natural lag time between that
|
||||
# and the viewers' RequestMultipleObjects messages
|
||||
self._cache_miss_timer = asyncio.get_event_loop().call_later(
|
||||
0.2, self._request_missed_cached_objects)
|
||||
loop = asyncio.get_event_loop_policy().get_event_loop()
|
||||
self._cache_miss_timer = loop.call_later(0.2, self._request_missed_cached_objects, report_only)
|
||||
|
||||
def _request_missed_cached_objects(self):
|
||||
def _request_missed_cached_objects(self, report_only: bool):
|
||||
self._cache_miss_timer = None
|
||||
self.request_objects(self.queued_cache_misses)
|
||||
if not self.queued_cache_misses:
|
||||
# All the queued cache misses ended up being satisfied without us
|
||||
# having to request them, no need to fire off a request.
|
||||
return
|
||||
if report_only:
|
||||
print(f"Would have automatically requested {self.queued_cache_misses!r}")
|
||||
else:
|
||||
self.request_objects(self.queued_cache_misses)
|
||||
self.queued_cache_misses.clear()
|
||||
|
||||
def clear(self):
|
||||
@@ -110,9 +117,12 @@ class ProxyWorldObjectManager(ClientWorldObjectManager):
|
||||
)
|
||||
|
||||
def _handle_object_update_cached_misses(self, region_handle: int, missing_locals: Set[int]):
|
||||
region_mgr: Optional[ProxyObjectManager] = self._get_region_manager(region_handle)
|
||||
if not self._settings.ALLOW_AUTO_REQUEST_OBJECTS:
|
||||
return
|
||||
if self._settings.AUTOMATICALLY_REQUEST_MISSING_OBJECTS:
|
||||
if self._settings.USE_VIEWER_OBJECT_CACHE:
|
||||
region_mgr.queued_cache_misses |= missing_locals
|
||||
region_mgr.request_missed_cached_objects_soon(report_only=True)
|
||||
elif self._settings.AUTOMATICALLY_REQUEST_MISSING_OBJECTS:
|
||||
# Schedule these local IDs to be requested soon if the viewer doesn't request
|
||||
# them itself. Ideally we could just mutate the CRC of the ObjectUpdateCached
|
||||
# to force a CRC cache miss in the viewer, but that appears to cause the viewer
|
||||
|
||||
@@ -63,8 +63,14 @@ class TaskScheduler:
|
||||
def shutdown(self):
|
||||
for task_data, task in self.tasks:
|
||||
task.cancel()
|
||||
await_all = asyncio.gather(*(task for task_data, task in self.tasks))
|
||||
asyncio.get_event_loop().run_until_complete(await_all)
|
||||
|
||||
try:
|
||||
event_loop = asyncio.get_running_loop()
|
||||
await_all = asyncio.gather(*(task for task_data, task in self.tasks))
|
||||
event_loop.run_until_complete(await_all)
|
||||
except RuntimeError:
|
||||
pass
|
||||
self.tasks.clear()
|
||||
|
||||
def _task_done(self, task: asyncio.Task):
|
||||
for task_details in reversed(self.tasks):
|
||||
|
||||
@@ -14,7 +14,7 @@ from hippolyzer.lib.proxy.transport import SOCKS5UDPTransport
|
||||
|
||||
|
||||
class BaseProxyTest(unittest.IsolatedAsyncioTestCase):
|
||||
def setUp(self) -> None:
|
||||
async def asyncSetUp(self) -> None:
|
||||
self.client_addr = ("127.0.0.1", 1)
|
||||
self.region_addr = ("127.0.0.1", 3)
|
||||
self.circuit_code = 1234
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import abc
|
||||
|
||||
from mitmproxy.addons import asgiapp
|
||||
from mitmproxy.controller import DummyReply
|
||||
|
||||
from hippolyzer.lib.proxy.addon_utils import BaseAddon
|
||||
from hippolyzer.lib.proxy.http_flow import HippoHTTPFlow
|
||||
@@ -11,13 +10,7 @@ from hippolyzer.lib.proxy.sessions import Session, SessionManager
|
||||
|
||||
async def serve(app, flow: HippoHTTPFlow):
|
||||
"""Serve a request based on a Hippolyzer HTTP flow using a provided app"""
|
||||
# Shove this on mitmproxy's flow object so asgiapp doesn't explode when it tries
|
||||
# to commit the flow reply. Our take / commit semantics are different than mitmproxy
|
||||
# proper, so we ignore what mitmproxy sets here anyhow.
|
||||
flow.flow.reply = DummyReply()
|
||||
flow.flow.reply.take()
|
||||
await asgiapp.serve(app, flow.flow)
|
||||
flow.flow.reply = None
|
||||
# Send the modified flow object back to mitmproxy
|
||||
flow.resume()
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ certifi==2021.10.8
|
||||
cffi==1.15.0
|
||||
charset-normalizer==2.0.9
|
||||
click==8.0.3
|
||||
cryptography==3.4.8
|
||||
cryptography==36.0.2
|
||||
defusedxml==0.7.1
|
||||
Flask==2.0.2
|
||||
frozenlist==1.2.0
|
||||
@@ -30,7 +30,7 @@ ldap3==2.9.1
|
||||
llbase==1.2.11
|
||||
lxml==4.6.4
|
||||
MarkupSafe==2.0.1
|
||||
mitmproxy==7.0.4
|
||||
mitmproxy==8.0.0
|
||||
msgpack==1.0.3
|
||||
multidict==5.2.0
|
||||
numpy==1.21.4
|
||||
@@ -43,7 +43,7 @@ publicsuffix2==2.20191221
|
||||
pyasn1==0.4.8
|
||||
pycparser==2.21
|
||||
Pygments==2.10.0
|
||||
pyOpenSSL==20.0.1
|
||||
pyOpenSSL==22.0.0
|
||||
pyparsing==2.4.7
|
||||
pyperclip==1.8.2
|
||||
PySide6==6.2.2
|
||||
|
||||
4
setup.py
4
setup.py
@@ -25,7 +25,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
here = path.abspath(path.dirname(__file__))
|
||||
|
||||
version = '0.9.0'
|
||||
version = '0.10.0'
|
||||
|
||||
with open(path.join(here, 'README.md')) as readme_fh:
|
||||
readme = readme_fh.read()
|
||||
@@ -89,7 +89,7 @@ setup(
|
||||
# requests breaks with newer idna
|
||||
'idna<3,>=2.5',
|
||||
# 7.x will be a major change.
|
||||
'mitmproxy>=7.0.2,<8.0',
|
||||
'mitmproxy>=8.0.0,<8.1',
|
||||
# For REPLs
|
||||
'ptpython<4.0',
|
||||
# JP2 codec
|
||||
|
||||
@@ -28,7 +28,8 @@ class MockHandlingCircuit(ProxiedCircuit):
|
||||
self.handler = handler
|
||||
|
||||
def _send_prepared_message(self, message: Message, transport=None):
|
||||
asyncio.get_event_loop().call_soon(self.handler.handle, message)
|
||||
loop = asyncio.get_event_loop_policy().get_event_loop()
|
||||
loop.call_soon(self.handler.handle, message)
|
||||
|
||||
|
||||
class MockConnectionHolder(ConnectionHolder):
|
||||
|
||||
@@ -62,8 +62,8 @@ addons = [ChildAddon()]
|
||||
|
||||
|
||||
class AddonIntegrationTests(BaseProxyTest):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
async def asyncSetUp(self) -> None:
|
||||
await super().asyncSetUp()
|
||||
self.addon = MockAddon()
|
||||
AddonManager.init([], self.session_manager, [self.addon], swallow_addon_exceptions=False)
|
||||
self.temp_dir = TemporaryDirectory(prefix="addon_test_sources")
|
||||
|
||||
@@ -30,8 +30,8 @@ class MockAddon(BaseAddon):
|
||||
|
||||
|
||||
class HTTPIntegrationTests(BaseProxyTest):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
async def asyncSetUp(self) -> None:
|
||||
await super().asyncSetUp()
|
||||
self.addon = MockAddon()
|
||||
AddonManager.init([], self.session_manager, [self.addon])
|
||||
self.flow_context = self.session_manager.flow_context
|
||||
@@ -124,8 +124,8 @@ class HTTPIntegrationTests(BaseProxyTest):
|
||||
|
||||
|
||||
class TestCapsClient(BaseProxyTest):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
async def asyncSetUp(self) -> None:
|
||||
await super().asyncSetUp()
|
||||
self._setup_default_circuit()
|
||||
self.caps_client = self.session.main_region.caps_client
|
||||
|
||||
@@ -141,8 +141,8 @@ class TestCapsClient(BaseProxyTest):
|
||||
|
||||
|
||||
class TestMITMProxy(BaseProxyTest):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
async def asyncSetUp(self) -> None:
|
||||
await super().asyncSetUp()
|
||||
self._setup_default_circuit()
|
||||
self.caps_client = self.session.main_region.caps_client
|
||||
proxy_port = 9905
|
||||
|
||||
@@ -47,8 +47,8 @@ class SimpleMessageLogger(FilteringMessageLogger):
|
||||
|
||||
|
||||
class LLUDPIntegrationTests(BaseProxyTest):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
async def asyncSetUp(self) -> None:
|
||||
await super().asyncSetUp()
|
||||
self.addon = MockAddon()
|
||||
self.deserializer = UDPMessageDeserializer()
|
||||
AddonManager.init([], self.session_manager, [self.addon])
|
||||
|
||||
@@ -7,8 +7,8 @@ from hippolyzer.lib.proxy.test_utils import BaseProxyTest
|
||||
|
||||
|
||||
class TestHTTPFlows(BaseProxyTest):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
async def asyncSetUp(self) -> None:
|
||||
await super().asyncSetUp()
|
||||
self.region = self.session.register_region(
|
||||
("127.0.0.1", 2),
|
||||
"https://test.localhost:4/foo",
|
||||
@@ -19,7 +19,7 @@ class TestHTTPFlows(BaseProxyTest):
|
||||
"ViewerAsset": "http://assets.example.com",
|
||||
})
|
||||
|
||||
def test_request_formatting(self):
|
||||
async def test_request_formatting(self):
|
||||
req = tutils.treq(host="example.com", port=80)
|
||||
fake_flow = tflow.tflow(req=req, resp=tutils.tresp())
|
||||
flow = HippoHTTPFlow.from_state(fake_flow.get_state(), self.session_manager)
|
||||
@@ -33,7 +33,7 @@ content-length: 7\r
|
||||
\r
|
||||
content""")
|
||||
|
||||
def test_binary_request_formatting(self):
|
||||
async def test_binary_request_formatting(self):
|
||||
req = tutils.treq(host="example.com", port=80)
|
||||
fake_flow = tflow.tflow(req=req, resp=tutils.tresp())
|
||||
flow = HippoHTTPFlow.from_state(fake_flow.get_state(), self.session_manager)
|
||||
@@ -47,7 +47,7 @@ X-Hippo-Escaped-Body: 1\r
|
||||
\r
|
||||
c\\x00ntent""")
|
||||
|
||||
def test_llsd_response_formatting(self):
|
||||
async def test_llsd_response_formatting(self):
|
||||
fake_flow = tflow.tflow(req=tutils.treq(), resp=tutils.tresp())
|
||||
flow = HippoHTTPFlow.from_state(fake_flow.get_state(), self.session_manager)
|
||||
# Half the time LLSD is sent with a random Content-Type and no PI indicating
|
||||
@@ -64,7 +64,7 @@ content-length: 33\r
|
||||
</llsd>
|
||||
""")
|
||||
|
||||
def test_flow_state_serde(self):
|
||||
async def test_flow_state_serde(self):
|
||||
fake_flow = tflow.tflow(req=tutils.treq(host="example.com"), resp=tutils.tresp())
|
||||
flow = HippoHTTPFlow.from_state(fake_flow.get_state(), self.session_manager)
|
||||
# Make sure cap resolution works correctly
|
||||
@@ -73,7 +73,7 @@ content-length: 33\r
|
||||
new_flow = HippoHTTPFlow.from_state(flow_state, self.session_manager)
|
||||
self.assertIs(self.session, new_flow.cap_data.session())
|
||||
|
||||
def test_http_asset_repo(self):
|
||||
async def test_http_asset_repo(self):
|
||||
asset_repo = self.session_manager.asset_repo
|
||||
asset_id = asset_repo.create_asset(b"foobar", one_shot=True)
|
||||
req = tutils.treq(host="assets.example.com", path=f"/?animatn_id={asset_id}")
|
||||
@@ -84,7 +84,7 @@ content-length: 33\r
|
||||
self.assertTrue(asset_repo.try_serve_asset(flow))
|
||||
self.assertEqual(b"foobar", flow.response.content)
|
||||
|
||||
def test_temporary_cap_resolution(self):
|
||||
async def test_temporary_cap_resolution(self):
|
||||
self.region.register_cap("TempExample", "http://not.example.com", CapType.TEMPORARY)
|
||||
self.region.register_cap("TempExample", "http://not2.example.com", CapType.TEMPORARY)
|
||||
# Resolving the cap should consume it
|
||||
|
||||
@@ -130,7 +130,7 @@ class MessageFilterTests(unittest.IsolatedAsyncioTestCase):
|
||||
# Make sure numbers outside 32bit range come through
|
||||
self.assertTrue(self._filter_matches("Foo.Bar.Foo == 0xFFffFFffFF", msg))
|
||||
|
||||
def test_http_flow(self):
|
||||
async def test_http_flow(self):
|
||||
session_manager = SessionManager(ProxySettings())
|
||||
fake_flow = tflow.tflow(req=tutils.treq(), resp=tutils.tresp())
|
||||
fake_flow.metadata["cap_data_ser"] = SerializedCapData(
|
||||
@@ -141,7 +141,7 @@ class MessageFilterTests(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertTrue(self._filter_matches("FakeCap", entry))
|
||||
self.assertFalse(self._filter_matches("NotFakeCap", entry))
|
||||
|
||||
def test_http_header_filter(self):
|
||||
async def test_http_header_filter(self):
|
||||
session_manager = SessionManager(ProxySettings())
|
||||
fake_flow = tflow.tflow(req=tutils.treq(), resp=tutils.tresp())
|
||||
fake_flow.request.headers["Cookie"] = 'foo="bar"'
|
||||
@@ -151,7 +151,7 @@ class MessageFilterTests(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertTrue(self._filter_matches('Meta.ReqHeaders.cookie ~= "foo"', entry))
|
||||
self.assertFalse(self._filter_matches('Meta.ReqHeaders.foobar ~= "foo"', entry))
|
||||
|
||||
def test_export_import_http_flow(self):
|
||||
async def test_export_import_http_flow(self):
|
||||
fake_flow = tflow.tflow(req=tutils.treq(), resp=tutils.tresp())
|
||||
fake_flow.metadata["cap_data_ser"] = SerializedCapData(
|
||||
cap_name="FakeCap",
|
||||
|
||||
@@ -29,7 +29,7 @@ class PacketIDTests(unittest.IsolatedAsyncioTestCase):
|
||||
msg.direction = Direction.OUT if outgoing else Direction.IN
|
||||
return self.circuit.send(msg)
|
||||
|
||||
def test_basic(self):
|
||||
async def test_basic(self):
|
||||
self._send_message(Message('ChatFromViewer', packet_id=1))
|
||||
self._send_message(Message('ChatFromViewer', packet_id=2))
|
||||
|
||||
@@ -38,7 +38,7 @@ class PacketIDTests(unittest.IsolatedAsyncioTestCase):
|
||||
(2, "ChatFromViewer", Direction.OUT, False, ()),
|
||||
))
|
||||
|
||||
def test_inject(self):
|
||||
async def test_inject(self):
|
||||
self._send_message(Message('ChatFromViewer', packet_id=1))
|
||||
self._send_message(Message('ChatFromViewer'))
|
||||
self._send_message(Message('ChatFromViewer', packet_id=2))
|
||||
@@ -49,7 +49,7 @@ class PacketIDTests(unittest.IsolatedAsyncioTestCase):
|
||||
(3, "ChatFromViewer", Direction.OUT, False, ()),
|
||||
))
|
||||
|
||||
def test_max_injected(self):
|
||||
async def test_max_injected(self):
|
||||
self._send_message(Message('ChatFromViewer', packet_id=1))
|
||||
for _ in range(5):
|
||||
self._send_message(Message('ChatFromViewer'))
|
||||
@@ -74,7 +74,7 @@ class PacketIDTests(unittest.IsolatedAsyncioTestCase):
|
||||
# Make sure we're still able to get the original ID
|
||||
self.assertEqual(self.circuit.out_injections.get_original_id(15), 3)
|
||||
|
||||
def test_inject_hole_in_sequence(self):
|
||||
async def test_inject_hole_in_sequence(self):
|
||||
self._send_message(Message('ChatFromViewer', packet_id=1))
|
||||
self._send_message(Message('ChatFromViewer'))
|
||||
self._send_message(Message('ChatFromViewer', packet_id=4))
|
||||
@@ -87,7 +87,7 @@ class PacketIDTests(unittest.IsolatedAsyncioTestCase):
|
||||
(6, "ChatFromViewer", Direction.OUT, True, ()),
|
||||
))
|
||||
|
||||
def test_inject_misordered(self):
|
||||
async def test_inject_misordered(self):
|
||||
self._send_message(Message('ChatFromViewer', packet_id=2))
|
||||
self._send_message(Message('ChatFromViewer'))
|
||||
self._send_message(Message('ChatFromViewer', packet_id=1))
|
||||
@@ -98,7 +98,7 @@ class PacketIDTests(unittest.IsolatedAsyncioTestCase):
|
||||
(1, "ChatFromViewer", Direction.OUT, False, ()),
|
||||
])
|
||||
|
||||
def test_inject_multiple(self):
|
||||
async def test_inject_multiple(self):
|
||||
self._send_message(Message('ChatFromViewer', packet_id=1))
|
||||
self._send_message(Message('ChatFromViewer'))
|
||||
self._send_message(Message('ChatFromViewer'))
|
||||
@@ -115,7 +115,7 @@ class PacketIDTests(unittest.IsolatedAsyncioTestCase):
|
||||
(6, "ChatFromViewer", Direction.OUT, True, ()),
|
||||
])
|
||||
|
||||
def test_packet_ack_field_converted(self):
|
||||
async def test_packet_ack_field_converted(self):
|
||||
self._send_message(Message('ChatFromViewer', packet_id=1))
|
||||
self._send_message(Message('ChatFromViewer'))
|
||||
self._send_message(Message('ChatFromViewer'))
|
||||
@@ -139,7 +139,7 @@ class PacketIDTests(unittest.IsolatedAsyncioTestCase):
|
||||
(6, "ChatFromViewer", Direction.OUT, True, ()),
|
||||
])
|
||||
|
||||
def test_packet_ack_proxied_message_converted(self):
|
||||
async def test_packet_ack_proxied_message_converted(self):
|
||||
self._send_message(Message('ChatFromViewer', packet_id=1))
|
||||
self._send_message(Message('ChatFromViewer'))
|
||||
self._send_message(Message('ChatFromViewer'))
|
||||
@@ -176,7 +176,7 @@ class PacketIDTests(unittest.IsolatedAsyncioTestCase):
|
||||
|
||||
self.assertEqual(self.circuit.sent_msgs[5]["Packets"][0]["ID"], 2)
|
||||
|
||||
def test_drop_proxied_message(self):
|
||||
async def test_drop_proxied_message(self):
|
||||
self._send_message(Message('ChatFromViewer', packet_id=1))
|
||||
self.circuit.drop_message(Message('ChatFromViewer', packet_id=2, flags=PacketFlags.RELIABLE))
|
||||
self._send_message(Message('ChatFromViewer', packet_id=3))
|
||||
@@ -188,7 +188,7 @@ class PacketIDTests(unittest.IsolatedAsyncioTestCase):
|
||||
])
|
||||
self.assertEqual(self.circuit.sent_msgs[1]["Packets"][0]["ID"], 2)
|
||||
|
||||
def test_unreliable_proxied_message(self):
|
||||
async def test_unreliable_proxied_message(self):
|
||||
self._send_message(Message('ChatFromViewer', packet_id=1))
|
||||
self.circuit.drop_message(Message('ChatFromViewer', packet_id=2))
|
||||
self._send_message(Message('ChatFromViewer', packet_id=3))
|
||||
@@ -198,7 +198,7 @@ class PacketIDTests(unittest.IsolatedAsyncioTestCase):
|
||||
(3, "ChatFromViewer", Direction.OUT, False, ()),
|
||||
])
|
||||
|
||||
def test_dropped_proxied_message_acks_sent(self):
|
||||
async def test_dropped_proxied_message_acks_sent(self):
|
||||
self._send_message(Message('ChatFromViewer', packet_id=1))
|
||||
self._send_message(Message('ChatFromViewer', packet_id=2))
|
||||
self._send_message(Message('ChatFromViewer', packet_id=3))
|
||||
@@ -220,7 +220,7 @@ class PacketIDTests(unittest.IsolatedAsyncioTestCase):
|
||||
# We injected an incoming packet, so "4" is really "3"
|
||||
self.assertEqual(self.circuit.sent_msgs[4]["Packets"][0]["ID"], 3)
|
||||
|
||||
def test_resending_or_dropping(self):
|
||||
async def test_resending_or_dropping(self):
|
||||
self.circuit.send(Message('ChatFromViewer', packet_id=1))
|
||||
to_drop = Message('ChatFromViewer', packet_id=2, flags=PacketFlags.RELIABLE)
|
||||
self.circuit.drop_message(to_drop)
|
||||
@@ -239,13 +239,13 @@ class PacketIDTests(unittest.IsolatedAsyncioTestCase):
|
||||
(2, "ChatFromViewer", Direction.OUT, True, ()),
|
||||
])
|
||||
|
||||
def test_reliable_unacked_queueing(self):
|
||||
async def test_reliable_unacked_queueing(self):
|
||||
self._send_message(Message('ChatFromViewer', flags=PacketFlags.RELIABLE))
|
||||
self._send_message(Message('ChatFromViewer', flags=PacketFlags.RELIABLE, packet_id=2))
|
||||
# Only the first, injected message should be queued for resends
|
||||
self.assertEqual({(Direction.OUT, 1)}, set(self.circuit.unacked_reliable))
|
||||
|
||||
def test_reliable_resend_cadence(self):
|
||||
async def test_reliable_resend_cadence(self):
|
||||
self._send_message(Message('ChatFromViewer', flags=PacketFlags.RELIABLE))
|
||||
resend_info = self.circuit.unacked_reliable[(Direction.OUT, 1)]
|
||||
self.circuit.resend_unacked()
|
||||
@@ -265,7 +265,7 @@ class PacketIDTests(unittest.IsolatedAsyncioTestCase):
|
||||
# Should have used up all the retry attempts and been kicked out of the retry queue
|
||||
self.assertEqual(set(), set(self.circuit.unacked_reliable))
|
||||
|
||||
def test_reliable_ack_collection(self):
|
||||
async def test_reliable_ack_collection(self):
|
||||
msg = Message('ChatFromViewer', flags=PacketFlags.RELIABLE)
|
||||
fut = self.circuit.send_reliable(msg)
|
||||
self.assertEqual(1, len(self.circuit.unacked_reliable))
|
||||
@@ -280,7 +280,7 @@ class PacketIDTests(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertEqual(0, len(self.circuit.unacked_reliable))
|
||||
self.assertTrue(fut.done())
|
||||
|
||||
def test_start_ping_check(self):
|
||||
async def test_start_ping_check(self):
|
||||
# Should not break if no unacked
|
||||
self._send_message(Message(
|
||||
"StartPingCheck",
|
||||
|
||||
@@ -55,8 +55,8 @@ class ObjectTrackingAddon(BaseAddon):
|
||||
|
||||
|
||||
class ObjectManagerTestMixin(BaseProxyTest):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
async def asyncSetUp(self) -> None:
|
||||
await super().asyncSetUp()
|
||||
self._setup_default_circuit()
|
||||
self.region = self.session.main_region
|
||||
self.message_handler = WrappingMessageHandler(self.region)
|
||||
@@ -418,13 +418,13 @@ class RegionObjectManagerTests(ObjectManagerTestMixin, unittest.IsolatedAsyncioT
|
||||
'AngularVelocity': Vector3(0.0, 0.0, 0.0791015625),
|
||||
'TreeSpecies': None,
|
||||
'ScratchPad': None,
|
||||
'Text': None,
|
||||
'TextColor': None,
|
||||
'MediaURL': None,
|
||||
'Sound': None,
|
||||
'SoundGain': None,
|
||||
'SoundFlags': None,
|
||||
'SoundRadius': None,
|
||||
'Text': b'',
|
||||
'TextColor': b'',
|
||||
'MediaURL': b'',
|
||||
'Sound': UUID(),
|
||||
'SoundGain': 0.0,
|
||||
'SoundFlags': 0,
|
||||
'SoundRadius': 0.0,
|
||||
'NameValue': [],
|
||||
'PathCurve': 32,
|
||||
'ProfileCurve': 0,
|
||||
@@ -505,8 +505,8 @@ class RegionObjectManagerTests(ObjectManagerTestMixin, unittest.IsolatedAsyncioT
|
||||
|
||||
|
||||
class SessionObjectManagerTests(ObjectManagerTestMixin, unittest.IsolatedAsyncioTestCase):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
async def asyncSetUp(self) -> None:
|
||||
await super().asyncSetUp()
|
||||
self.second_region = self.session.register_region(
|
||||
("127.0.0.1", 9), "https://localhost:5", 124
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user