aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests/test_webrtc_transport.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/tests/test_webrtc_transport.py')
-rw-r--r--packages/meshbay-node/tests/test_webrtc_transport.py64
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()