diff options
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/transport/webrtc/chat.py | 26 | ||||
| -rw-r--r-- | packages/meshbay-node/tests/test_reply_correlation.py | 27 |
2 files changed, 44 insertions, 9 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/transport/webrtc/chat.py b/packages/meshbay-node/src/meshbay_node/transport/webrtc/chat.py index f200884..ed2c43a 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/webrtc/chat.py +++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc/chat.py @@ -20,6 +20,7 @@ from meshbay_common.protocol import MNP from meshbay_node import linkpreview, ops from meshbay_node.chat import FORMAT_SEALED_V1, ReplayedMessage +from meshbay_node.transport.webrtc.channel import _REPLY_TO log = logging.getLogger("meshbay_node.transport.webrtc_server") @@ -115,15 +116,22 @@ class ChatMixin: self._audit("chat_epoch_failed", reason) return self._audit("chat_epoch", f"{reason}:{result['epoch']}") - # Everyone still connected picks the new key up without reconnecting. - for session in list( - (self._ctx.get("groups") or {}).get(group_id, {}) - .get("_peers", {}).values()): - try: - session._send({"type": MNP.CHAT_EPOCH_ACK, "v": MNP_VERSION, - "epoch": result["epoch"]}) - except Exception: - pass + # Everyone still connected picks the new key up without reconnecting — + # this session too, which is why the notice leaves outside the reply + # context: stamped with the id of the request that caused it (a device + # revoked, a member removed), it would arrive as that request's answer. + token = _REPLY_TO.set((None, None)) + try: + for session in list( + (self._ctx.get("groups") or {}).get(group_id, {}) + .get("_peers", {}).values()): + try: + session._send({"type": MNP.CHAT_EPOCH_ACK, "v": MNP_VERSION, + "epoch": result["epoch"]}) + except Exception: + pass + finally: + _REPLY_TO.reset(token) def _do_chat_directory(self, msg: dict) -> None: """ diff --git a/packages/meshbay-node/tests/test_reply_correlation.py b/packages/meshbay-node/tests/test_reply_correlation.py index bfb336e..a97ec1e 100644 --- a/packages/meshbay-node/tests/test_reply_correlation.py +++ b/packages/meshbay-node/tests/test_reply_correlation.py @@ -28,6 +28,7 @@ import base64 import msgpack import pytest from meshbay_common.protocol import MNP +from meshbay_node.transport.webrtc.channel import _REPLY_TO from meshbay_node.transport.webrtc_server import WebRTCPeerSession pytestmark = pytest.mark.asyncio @@ -163,3 +164,29 @@ async def test_work_handed_to_a_task_still_answers_under_the_right_id(): by_id = {m["req_id"]: m["detail"] for m in s._channel.sent} assert by_id == {11: "first", 12: "second"}, ( "a late reply answered under whichever request arrived most recently") + + +async def test_a_new_chat_epoch_answers_no_request_not_even_the_askers(): + """Revoking a device or removing a member opens a chat epoch, announced to + everyone connected — the asker included. Stamped with the asker's request, + that notice arrived as the answer to `device_revoke` before the real one.""" + asker, other = _session("asker"), _session("other") + asker._ctx = {"groups": {"g": {"_peers": {"a": asker, "b": other}}}} + asker._audit = lambda *a, **k: None + + async def run_op(fn, *args): + return {"epoch": 2} + asker._run_op = run_op + + token = _REPLY_TO.set((asker, 9)) # inside the handler of request 9 + try: + await asker._new_chat_epoch("g", "device_revoke") + asker._send({"type": MNP.DEVICE_ADD_ACK}) + finally: + _REPLY_TO.reset(token) + + notice, reply = asker._channel.sent + assert notice["type"] == MNP.CHAT_EPOCH_ACK and "req_id" not in notice + assert reply["type"] == MNP.DEVICE_ADD_ACK and reply["req_id"] == 9 + (seen,) = other._channel.sent + assert "req_id" not in seen |