diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-03 15:20:40 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-03 15:20:40 +0200 |
| commit | c1be7571973c3d0b671ed4db2da41266ae3099d8 (patch) | |
| tree | d2b98bf33727c5905669c9c3f40edba30db11ac3 /packages/meshbay-node/tests/test_multi_group.py | |
| parent | 691c6ba4ef51085c89aeddbcabd5c733861eb56b (diff) | |
| download | meshbay-c1be7571973c3d0b671ed4db2da41266ae3099d8.tar.gz | |
refactor!: one file_chunk and index_sync encoder for every transport
`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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AsoWC3GmhNdwVFomW3QjH3
Diffstat (limited to 'packages/meshbay-node/tests/test_multi_group.py')
| -rw-r--r-- | packages/meshbay-node/tests/test_multi_group.py | 32 |
1 files changed, 17 insertions, 15 deletions
diff --git a/packages/meshbay-node/tests/test_multi_group.py b/packages/meshbay-node/tests/test_multi_group.py index 2828d08..3dc4778 100644 --- a/packages/meshbay-node/tests/test_multi_group.py +++ b/packages/meshbay-node/tests/test_multi_group.py @@ -16,7 +16,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 from meshbay_node.transport.quic_client import QuicChunkClient @@ -79,9 +79,14 @@ async def multi_group_server(sk_node, sk_hub, gek_a, gek_b, dir_a, dir_b, tmp_pa indexer_b = DirectoryIndexer(roots=one_root(dir_b), group_id="group-b", sk_node=sk_node, gek=gek_b) await indexer_b.initial_scan() + # RootSet, not a bare Path — what the daemon actually puts in a group context. + # This held a Path until 2026-09-03 and nothing noticed: the QUIC index handler + # only called `index.serialize()`, and `entry_abs_path` fell through + # `Path.resolve(strict=...)`, reading the virtual path as a truthy flag and + # returning the right file by accident. groups = { - "group-a": {"gek": gek_a, "roots": dir_a, "index": indexer_a.index}, - "group-b": {"gek": gek_b, "roots": dir_b, "index": indexer_b.index}, + "group-a": {"gek": gek_a, "roots": one_root(dir_a), "index": indexer_a.index}, + "group-b": {"gek": gek_b, "roots": one_root(dir_b), "index": indexer_b.index}, } cert_path = tmp_path / "node.crt" @@ -113,13 +118,12 @@ async def test_user_can_access_own_group( pk_node_b64=pk_to_b64(sk_node.public_key()), group_id="group-a", ) as client: - wire = await client.fetch_index() - recovered = GroupIndex.deserialize(wire, sk_node=sk_node, gek=gek_a) - assert recovered.count == 1 + msg = await client.fetch_index() + assert len(msg["entries"]) == 1 - entry = recovered.entries[0] - assert entry.name == "file_a.txt" - chunk = await client.fetch_chunk(entry.id, chunk_index=0) + entry = msg["entries"][0] + assert entry["name"] == "file_a.txt" + chunk = await client.fetch_chunk(entry["id"], chunk_index=0) assert chunk == b"content from group A " * 100 @@ -155,9 +159,8 @@ async def test_dual_group_user_accesses_both( pk_node_b64=pk_to_b64(sk_node.public_key()), group_id="group-a", ) as client_a: - wire_a = await client_a.fetch_index() - idx_a = GroupIndex.deserialize(wire_a, sk_node=sk_node, gek=gek_a) - assert idx_a.entries[0].name == "file_a.txt" + msg_a = await client_a.fetch_index() + assert msg_a["entries"][0]["name"] == "file_a.txt" async with QuicChunkClient( host="127.0.0.1", port=19200, @@ -165,6 +168,5 @@ async def test_dual_group_user_accesses_both( pk_node_b64=pk_to_b64(sk_node.public_key()), group_id="group-b", ) as client_b: - wire_b = await client_b.fetch_index() - idx_b = GroupIndex.deserialize(wire_b, sk_node=sk_node, gek=gek_b) - assert idx_b.entries[0].name == "file_b.txt" + msg_b = await client_b.fetch_index() + assert msg_b["entries"][0]["name"] == "file_b.txt" |