14 Commits

Author SHA1 Message Date
Salad Dais
2e7f887970 v0.10.0 2022-06-24 01:54:37 +00:00
Salad Dais
ef9df6b058 Update Windows bundling action to add artifact to release 2022-06-24 01:12:21 +00:00
Salad Dais
baae0f6d6e Fix TupleCoord negation 2022-06-21 07:15:49 +00:00
Salad Dais
0f369b682d Upgrade to mitmproxy 8.0
Not 8.1 since that drops Python 3.8 support. Closes #26
2022-06-20 15:15:57 +00:00
Salad Dais
1f1e4de254 Add addon for testing object manager conformance against viewer
Closes #18
2022-06-20 12:38:11 +00:00
Salad Dais
75ddc0a5ba Be smarter about object cache miss autorequests 2022-06-20 12:33:12 +00:00
Salad Dais
e4cb168138 Clear up last few event loop warnings 2022-06-20 12:31:08 +00:00
Salad Dais
63aebba754 Clear up some event loop deprecation warnings 2022-06-20 05:55:01 +00:00
Salad Dais
8cf1a43d59 Better defaults when parsing ObjectUpdateCompressed
This helps our view of the cache better match the viewer's VOCache
2022-06-20 03:23:46 +00:00
Salad Dais
bbc8813b61 Add unary minus for TupleCoords 2022-06-19 04:33:20 +00:00
Salad Dais
5b51dbd30f Add workaround instructions for most recent Firestorm release
Closes #25
2022-05-13 23:52:50 +00:00
Salad Dais
295c7972e7 Use windows-2019 runner instead of windows-latest
windows-latest has some weird ACL changes that cause the cx_Freeze
packaging steps to fail.
2022-05-13 23:39:37 +00:00
Salad Dais
b034661c38 Revert "Temporarily stop generating lib_licenses.txt automatically"
This reverts commit f12fd95ee1.
2022-05-13 23:39:09 +00:00
Salad Dais
f12fd95ee1 Temporarily stop generating lib_licenses.txt automatically
Something is busted with pip-licenses in CI. Not sure why, but
it's only needed for Windows builds anyway.
2022-03-12 19:15:59 +00:00
24 changed files with 265 additions and 91 deletions

View File

@@ -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 }}

View File

@@ -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.

View 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()]

View File

@@ -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

View File

@@ -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)))

View File

@@ -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():

View File

@@ -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

View File

@@ -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)

View File

@@ -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()

View File

@@ -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:

View File

@@ -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

View File

@@ -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):

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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):

View File

@@ -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")

View File

@@ -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

View File

@@ -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])

View File

@@ -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

View File

@@ -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",

View File

@@ -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",

View File

@@ -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
)