diff options
Diffstat (limited to 'packages/meshbay-node/tests/test_webrtc_transport.py')
| -rw-r--r-- | packages/meshbay-node/tests/test_webrtc_transport.py | 64 |
1 files changed, 52 insertions, 12 deletions
diff --git a/packages/meshbay-node/tests/test_webrtc_transport.py b/packages/meshbay-node/tests/test_webrtc_transport.py index dc74752..ea13d96 100644 --- a/packages/meshbay-node/tests/test_webrtc_transport.py +++ b/packages/meshbay-node/tests/test_webrtc_transport.py @@ -244,6 +244,35 @@ async def _open_channel(transport, peer_id): return pc, ch, q +def _sealed_chat(session, text: bytes = b"ciphertext") -> dict: + """ + A chat message in the shape MNP 2.0 requires, on a live session. + + There is no plaintext chat any more, so a test that wants to exercise + delivery has to send a real envelope. The bytes need not be a real + ciphertext — the node never opens one — but the envelope's shape and the + device claim are checked, and the device must be the one this connection + identified itself as. Identifying it here is what `device_hello` does over + the wire; doing it directly keeps this test about chat rather than about + device linking, which `test_device_on_connection.py` covers. + """ + device = hashlib.sha256(session._registry_key.encode()).digest() + session._pinned_pk = base64.b64encode(device).decode() + session._device_confirmed = True + return { + "type": MNP.CHAT_MESSAGE, "v": MNP_VERSION, + "format": 1, "epoch": 1, "device": device, "ct": text, + "nonce": b"\x02" * 12, "sig": b"\x03" * 64, + } + + +def _only_session(transport): + """The one live peer session on a transport, for tests that made one.""" + sessions = list(transport._sessions.values()) + assert len(sessions) == 1, f"expected one session, got {len(sessions)}" + return sessions[0] + + async def _setup_peer(transport, sk_hub, gek, peer_id, jwt_sub="user-001", sk_user=None, group_id=TEST_GROUP): """Create a peer connection, perform handshake with GEK proof, return (pc, channel, queue).""" @@ -574,11 +603,8 @@ async def test_webrtc_chat_send_and_history(sk_node, sk_hub, gek, shared_dir, tm browser_pc, channel, received = await _setup_peer( transport, sk_hub, gek, "peer-chat") - channel.send(_pack({ - "type": MNP.CHAT_MESSAGE, - "v": MNP_VERSION, - "payload": "hello from browser", - })) + channel.send(_pack(_sealed_chat(_only_session(transport), + b"hello from browser"))) chat_ack = await asyncio.wait_for(received.get(), timeout=5.0) assert chat_ack["type"] == "ack" @@ -593,7 +619,12 @@ async def test_webrtc_chat_send_and_history(sk_node, sk_hub, gek, shared_dir, tm hist = await asyncio.wait_for(received.get(), timeout=5.0) assert hist["type"] == MNP.CHAT_HISTORY_RESPONSE assert len(hist["messages"]) == 1 - assert hist["messages"][0]["payload"] == "hello from browser" + # The ciphertext comes back under `ct`, byte for byte — `payload` is the + # plaintext field and stays empty for a sealed row. Decoding a ciphertext + # as UTF-8, which the history path used to do, would mangle it. + assert hist["messages"][0]["ct"] == b"hello from browser" + assert hist["messages"][0]["payload"] == "" + assert hist["messages"][0]["format"] == 1 assert hist["messages"][0]["sender_id"] == "user-001" await chat_store.close() @@ -650,17 +681,22 @@ async def test_webrtc_chat_broadcast(sk_node, sk_hub, gek, shared_dir, tmp_path) pc_a, ch_a, q_a = await _setup_peer(transport, sk_hub, gek, "peer-A", "user-A") pc_b, ch_b, q_b = await _setup_peer(transport, sk_hub, gek, "peer-B", "user-B") - ch_a.send(_pack({ - "type": MNP.CHAT_MESSAGE, "v": MNP_VERSION, "payload": "hi from A", - })) + session_a = next(s for s in transport._sessions.values() + if s._user_id == "user-A") + ch_a.send(_pack(_sealed_chat(session_a, b"hi from A"))) ack_a = await asyncio.wait_for(q_a.get(), timeout=5.0) assert ack_a["type"] == "ack" broadcast = await asyncio.wait_for(q_b.get(), timeout=5.0) assert broadcast["type"] == MNP.CHAT_MESSAGE + # `sender_id` is still the node's, from the authenticated session (NS6). + # What it now carries beside it is the sending device and a signature over + # the ciphertext, which is what makes the claim checkable by the receiver + # rather than taken on the node's word. assert broadcast["sender_id"] == "user-A" - assert broadcast["payload"] == "hi from A" + assert broadcast["ct"] == b"hi from A" + assert broadcast["device"] == base64.b64decode(session_a._pinned_pk) await chat_store.close() await pc_a.close() @@ -731,12 +767,16 @@ async def test_webrtc_peer_cleanup_on_close(sk_node, sk_hub, gek, shared_dir): browser_pc, channel, received = await _setup_peer( transport, sk_hub, gek, "peer-cleanup") - assert "user-001" in transport._ctx["_peers"] + # Keyed per connection, not per account (docs/chat-sender-keys.md F7), so + # membership is asserted by the session object rather than by user_id — + # one account may hold several entries here. + peers = transport._ctx["_peers"] + assert [s._user_id for s in peers.values()] == ["user-001"] assert transport.active_peers == 1 await transport.close_peer("peer-cleanup") - assert "user-001" not in transport._ctx["_peers"] + assert transport._ctx["_peers"] == {} assert transport.active_peers == 0 await browser_pc.close() |