From c1be7571973c3d0b671ed4db2da41266ae3099d8 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Thu, 3 Sep 2026 15:20:40 +0200 Subject: refactor!: one file_chunk and index_sync encoder for every transport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `file_chunk` and `index_sync` were each built twice, once per transport, and the two copies did not agree. WebRTC sent binary, unsigned chunks carrying a `file_id`; QUIC sent base64 fields, two BLAKE3 hashes, a per-chunk Ed25519 signature and no `file_id`. `index_sync` was plain entries on one transport and a `GroupIndex.serialize()` envelope on the other. One message type, two shapes, one consumer each, and nothing that failed when they drifted — finding C6 one size down, in the two places the handshake unification did not reach. Phase 9.15 moved WebRTC to the binary format and dropped the per-chunk signature; the QUIC encoder was never brought along. It is dropped here rather than reintroduced: the AES-GCM tag authenticates the ciphertext under a GEK-derived key, and since C3 the node authenticates itself once in the handshake instead of once per megabyte. `meshbay_common.protocol` now owns the chunk codec (`chunk_ciphertext`, `file_chunk_wire`, `file_chunk_plaintext`) and `meshbay_node/transport/wire.py` the index builder, which also absorbs the delta the daemon used to hand-build. `test_transport_wire_parity.py` fails if either server grows its own copy back. `ChunkRequest`/`ChunkResponse` are deleted. `ChunkResponse` described the QUIC half while reading like the contract for both, which is what made the fork hard to see at all. BREAKING CHANGE: MNP 0.15 changes the encoding of `file_chunk` and `index_sync` on the QUIC transport. The WebRTC shapes are byte for byte unchanged and no QUIC client ships, which is why this is a MINOR bump; a deployed QUIC peer would have made it MAJOR. Also fixes a test fixture that put a `Path` where the daemon puts a `RootSet`. Nothing caught it: the old QUIC index handler never touched `roots`, and `entry_abs_path` fell through `Path.resolve(strict=...)`, reading the virtual path as a truthy flag and returning the right file by accident. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01AsoWC3GmhNdwVFomW3QjH3 --- packages/meshbay-node/tests/test_quic_transport.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) (limited to 'packages/meshbay-node/tests/test_quic_transport.py') diff --git a/packages/meshbay-node/tests/test_quic_transport.py b/packages/meshbay-node/tests/test_quic_transport.py index 5720043..fb28295 100644 --- a/packages/meshbay-node/tests/test_quic_transport.py +++ b/packages/meshbay-node/tests/test_quic_transport.py @@ -13,7 +13,7 @@ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey from cryptography.hazmat.primitives import serialization from meshbay_common.crypto import generate_gek, pk_to_b64 -from meshbay_node.indexer import DirectoryIndexer, GroupIndex +from meshbay_node.indexer import DirectoryIndexer from conftest import one_root from meshbay_node.transport.quic_server import QuicChunkServer, Denylist from meshbay_node.transport.quic_client import QuicChunkClient @@ -95,7 +95,7 @@ async def test_quic_chunk_roundtrip(sk_node, sk_hub, gek, shared_dir, tmp_path): @pytest.mark.asyncio async def test_quic_fetch_index(sk_node, sk_hub, gek, shared_dir, tmp_path): - """QUIC index sync returns deserializable GroupIndex.""" + """QUIC index sync returns the same message shape WebRTC sends.""" hub_pk_pem = sk_hub.public_key().public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo) @@ -121,9 +121,10 @@ async def test_quic_fetch_index(sk_node, sk_hub, gek, shared_dir, tmp_path): pk_node_b64=pk_to_b64(sk_node.public_key()), group_id="g", ) as client: - wire = await client.fetch_index() - recovered = GroupIndex.deserialize(wire, sk_node=sk_node, gek=gek) - assert recovered.count == 2 + msg = await client.fetch_index() + assert msg["group_id"] == "g" + assert len(msg["entries"]) == 2 + assert "dirs" in msg and "roots" in msg await server.stop() @@ -226,8 +227,7 @@ async def test_quic_session_resumption(sk_node, sk_hub, gek, shared_dir, tmp_pat jwt_token=token, gek=gek, pk_node_b64=pk_b64, group_id="g", ) as client: - wire = await client.fetch_index() - assert GroupIndex.deserialize(wire, sk_node=sk_node, gek=gek).count == 2 + assert len((await client.fetch_index())["entries"]) == 2 saved_ticket = client.session_ticket saved_cert = client.peer_cert_der @@ -244,8 +244,7 @@ async def test_quic_session_resumption(sk_node, sk_hub, gek, shared_dir, tmp_pat peer_cert_der=saved_cert, group_id="g", ) as client: - wire = await client.fetch_index() - assert GroupIndex.deserialize(wire, sk_node=sk_node, gek=gek).count == 2 + assert len((await client.fetch_index())["entries"]) == 2 await server.stop() @@ -281,8 +280,7 @@ async def test_quic_denylist_blocks_user(sk_node, sk_hub, gek, shared_dir, tmp_p jwt_token=token, gek=gek, pk_node_b64=pk_b64, group_id="g", ) as client: - wire = await client.fetch_index() - assert GroupIndex.deserialize(wire, sk_node=sk_node, gek=gek).count == 2 + assert len((await client.fetch_index())["entries"]) == 2 # Add user to denylist denylist.deny_user("user-001") -- cgit v1.2.3