gabriel / muse public
test_wire_framing.py python
375 lines 14.9 KB
Raw
sha256:51ce277f663e01a43eaffbe77509b1de7ac2d4251b55d23306304bcdeb92c90d feat(pack): delta-encode snapshots in MPackBundle wire format Sonnet 4.6 minor ⚠ breaking 121 days ago
1 """Tests for MuseWireFrameWriter — explicit content-addressed frame envelopes.
2
3 Test plan:
4 A. Writer/reader roundtrip — wrap H, O, C, E; decode back; verify
5 B. Truncated header — clean error, not msgpack garbage
6 C. Truncated payload — clean error, not msgpack garbage
7 D. Hash mismatch — deterministic rejection
8 E. Size mismatch — envelope sz vs binary length prefix
9 F. Envelope/logical mismatch — ft="C" but payload t="O"
10 I. Wall-5 regression — truncated C bytes never produce map32 garbage
11 """
12 from __future__ import annotations
13
14 import struct
15
16 import msgpack
17 import pytest
18 from muse.core.types import MsgpackDict, MsgpackValue, blob_id
19
20 # ---------------------------------------------------------------------------
21 # Helpers
22 # ---------------------------------------------------------------------------
23
24 def _pack(obj: MsgpackValue) -> bytes:
25 return msgpack.packb(obj, use_bin_type=True)
26
27
28 def _unpack(data: bytes) -> MsgpackValue:
29 return msgpack.unpackb(data, raw=False)
30
31
32 def _make_envelope(ft: str, payload: bytes) -> bytes:
33 """Build one wire frame using the spec layout directly (no writer)."""
34 from muse.core.types import blob_id
35 header = {"ft": ft, "id": blob_id(payload), "sz": len(payload)}
36 header_bytes = _pack(header)
37 return b"".join([
38 b"muse",
39 bytes([1]),
40 struct.pack(">I", len(header_bytes)),
41 header_bytes,
42 struct.pack(">Q", len(payload)),
43 payload,
44 ])
45
46
47 def _decode_envelope(data: bytes) -> tuple[dict, bytes]:
48 """Parse one envelope from bytes — for roundtrip assertions."""
49 assert data[:4] == b"muse", f"bad magic: {data[:4]!r}"
50 assert data[4] == 1
51 header_len = struct.unpack(">I", data[5:9])[0]
52 header = _unpack(data[9:9 + header_len])
53 payload_start = 9 + header_len + 8
54 payload_len = struct.unpack(">Q", data[9 + header_len:payload_start])[0]
55 payload = data[payload_start:payload_start + payload_len]
56 return header, payload
57
58
59 # ---------------------------------------------------------------------------
60 # A — MuseWireFrameWriter roundtrip
61 # ---------------------------------------------------------------------------
62
63 class TestWireFrameWriterRoundtrip:
64 """A. wrap() produces correctly framed bytes that decode back faithfully."""
65
66 def test_import(self) -> None:
67 from muse.core.mpack import MuseWireFrameWriter # noqa: F401
68
69 def test_wire_frame_error_import(self) -> None:
70 from muse.core.mpack import WireFrameError # noqa: F401
71
72 def test_wire_content_type_import(self) -> None:
73 from muse.core.mpack import WIRE_CONTENT_TYPE
74 assert WIRE_CONTENT_TYPE.startswith("application/")
75
76 def test_wrap_header_frame(self) -> None:
77 from muse.core.mpack import MuseWireFrameWriter
78 from muse.core.types import blob_id
79
80 payload = _pack({"t": "H", "branch": "main", "n_objects": 0})
81 fw = MuseWireFrameWriter()
82 wrapped = fw.wrap(frame_type="H", payload=payload)
83
84 assert wrapped[:4] == b"muse"
85 assert wrapped[4] == 1
86
87 header, decoded_payload = _decode_envelope(wrapped)
88 assert header["ft"] == "H"
89 assert header["sz"] == len(payload)
90 assert header["id"] == blob_id(payload)
91 assert decoded_payload == payload
92
93 def test_wrap_object_frame(self) -> None:
94 from muse.core.mpack import MuseWireFrameWriter
95
96 content = b"raw object bytes"
97 oid = blob_id(content)
98 payload = _pack({"t": "O", "id": oid, "content": content, "enc": "raw"})
99 fw = MuseWireFrameWriter()
100 wrapped = fw.wrap(frame_type="O", payload=payload)
101
102 header, decoded_payload = _decode_envelope(wrapped)
103 assert header["ft"] == "O"
104 decoded = _unpack(decoded_payload)
105 assert decoded["t"] == "O"
106 assert decoded["id"] == oid
107
108 def test_wrap_commit_pack_frame(self) -> None:
109 from muse.core.mpack import MuseWireFrameWriter
110
111 payload = _pack({"t": "C", "commits": [], "snapshots": []})
112 fw = MuseWireFrameWriter()
113 wrapped = fw.wrap(frame_type="C", payload=payload)
114
115 header, decoded_payload = _decode_envelope(wrapped)
116 assert header["ft"] == "C"
117 assert _unpack(decoded_payload)["t"] == "C"
118
119 def test_wrap_end_frame(self) -> None:
120 from muse.core.mpack import MuseWireFrameWriter
121
122 payload = _pack({"t": "E", "n_objects": 5, "n_commits": 3})
123 fw = MuseWireFrameWriter()
124 wrapped = fw.wrap(frame_type="E", payload=payload)
125
126 header, decoded_payload = _decode_envelope(wrapped)
127 assert header["ft"] == "E"
128 decoded = _unpack(decoded_payload)
129 assert decoded["n_objects"] == 5
130
131 def test_large_payload_size_field(self) -> None:
132 """sz and payload_len both encode the correct payload length."""
133 from muse.core.mpack import MuseWireFrameWriter
134
135 payload = b"x" * 65537 # > 2^16 to exercise uint64 path
136 fw = MuseWireFrameWriter()
137 wrapped = fw.wrap(frame_type="O", payload=payload)
138
139 header, decoded_payload = _decode_envelope(wrapped)
140 assert header["sz"] == 65537
141 assert len(decoded_payload) == 65537
142
143 def test_empty_payload(self) -> None:
144 from muse.core.mpack import MuseWireFrameWriter
145 from muse.core.types import blob_id
146
147 fw = MuseWireFrameWriter()
148 payload = b""
149 wrapped = fw.wrap(frame_type="E", payload=payload)
150 header, decoded = _decode_envelope(wrapped)
151 assert header["sz"] == 0
152 assert header["id"] == blob_id(b"")
153 assert decoded == b""
154
155 def test_wrap_produces_exact_layout(self) -> None:
156 """Manual layout check: every byte group is in the right position."""
157 from muse.core.mpack import MuseWireFrameWriter
158
159 payload = _pack({"t": "H"})
160 fw = MuseWireFrameWriter()
161 wrapped = fw.wrap(frame_type="H", payload=payload)
162
163 # magic
164 assert wrapped[0:4] == b"muse"
165 # version
166 assert wrapped[4:5] == bytes([1])
167 # header_len (big-endian uint32)
168 header_len = struct.unpack(">I", wrapped[5:9])[0]
169 assert header_len > 0
170 # header is valid msgpack
171 header = _unpack(wrapped[9:9 + header_len])
172 assert "ft" in header and "id" in header and "sz" in header
173 # payload_len (big-endian uint64)
174 offset = 9 + header_len
175 payload_len = struct.unpack(">Q", wrapped[offset:offset + 8])[0]
176 assert payload_len == len(payload)
177 # payload bytes
178 assert wrapped[offset + 8:offset + 8 + payload_len] == payload
179
180 def test_total_frame_byte_length(self) -> None:
181 """Total wrapped length == 4+1+4+header_len+8+payload_len."""
182 from muse.core.mpack import MuseWireFrameWriter
183
184 payload = _pack({"t": "H", "branch": "dev"})
185 fw = MuseWireFrameWriter()
186 wrapped = fw.wrap(frame_type="H", payload=payload)
187
188 header_len = struct.unpack(">I", wrapped[5:9])[0]
189 expected_len = 4 + 1 + 4 + header_len + 8 + len(payload)
190 assert len(wrapped) == expected_len
191
192
193 # ---------------------------------------------------------------------------
194 # D — Hash mismatch
195 # ---------------------------------------------------------------------------
196
197 class TestHashMismatch:
198 """D. Tampered payload produces deterministic hash mismatch error."""
199
200 def _tamper(self, wrapped: bytes) -> bytes:
201 """Flip the last byte of the payload."""
202 return wrapped[:-1] + bytes([wrapped[-1] ^ 0xFF])
203
204 def test_tampered_frame_has_wrong_hash(self) -> None:
205 from muse.core.mpack import MuseWireFrameWriter
206 from muse.core.types import blob_id
207
208 payload = _pack({"t": "C", "commits": [], "snapshots": []})
209 fw = MuseWireFrameWriter()
210 wrapped = fw.wrap(frame_type="C", payload=payload)
211 tampered = self._tamper(wrapped)
212
213 _, decoded_payload = _decode_envelope(tampered)
214 header, _ = _decode_envelope(wrapped)
215
216 actual_id = blob_id(decoded_payload)
217 assert actual_id != header["id"], "tampered payload must not match original hash"
218
219 def test_untampered_frame_hash_matches(self) -> None:
220 from muse.core.mpack import MuseWireFrameWriter
221 from muse.core.types import blob_id
222
223 payload = _pack({"t": "C", "commits": [], "snapshots": []})
224 fw = MuseWireFrameWriter()
225 wrapped = fw.wrap(frame_type="C", payload=payload)
226 header, decoded_payload = _decode_envelope(wrapped)
227
228 assert blob_id(decoded_payload) == header["id"]
229
230
231 # ---------------------------------------------------------------------------
232 # E — Size mismatch (tamper envelope sz)
233 # ---------------------------------------------------------------------------
234
235 class TestSizeMismatch:
236 """E. Envelope sz must equal binary payload_len."""
237
238 def test_envelope_sz_matches_payload(self) -> None:
239 from muse.core.mpack import MuseWireFrameWriter
240
241 payload = _pack({"t": "H", "n_objects": 10})
242 fw = MuseWireFrameWriter()
243 wrapped = fw.wrap(frame_type="H", payload=payload)
244 header, decoded_payload = _decode_envelope(wrapped)
245
246 assert header["sz"] == len(decoded_payload)
247
248 def test_sz_mismatch_detected(self) -> None:
249 """Reader must reject a frame where envelope sz != binary payload_len."""
250 from muse.core.types import blob_id
251
252 payload = _pack({"t": "H", "n_objects": 10})
253 # Build a frame with tampered sz in the envelope
254 bad_header = {"ft": "H", "id": blob_id(payload), "sz": len(payload) + 99}
255 bad_header_bytes = _pack(bad_header)
256 tampered = b"".join([
257 b"muse",
258 bytes([1]),
259 struct.pack(">I", len(bad_header_bytes)),
260 bad_header_bytes,
261 struct.pack(">Q", len(payload)), # binary length is correct
262 payload,
263 ])
264 # The binary payload_len != envelope sz — a reader MUST reject this
265 header_len = struct.unpack(">I", tampered[5:9])[0]
266 h = _unpack(tampered[9:9 + header_len])
267 offset = 9 + header_len
268 pl = struct.unpack(">Q", tampered[offset:offset + 8])[0]
269 assert h["sz"] != pl, "tampered frame should have mismatched sz and payload_len"
270
271
272 # ---------------------------------------------------------------------------
273 # F — Envelope/logical type mismatch
274 # ---------------------------------------------------------------------------
275
276 class TestEnvelopeLogicalMismatch:
277 """F. envelope ft must match payload t."""
278
279 def test_matching_types_are_consistent(self) -> None:
280 from muse.core.mpack import MuseWireFrameWriter
281
282 payload = _pack({"t": "C", "commits": [], "snapshots": []})
283 fw = MuseWireFrameWriter()
284 wrapped = fw.wrap(frame_type="C", payload=payload)
285 header, decoded_payload = _decode_envelope(wrapped)
286 decoded = _unpack(decoded_payload)
287 assert header["ft"] == decoded["t"]
288
289 def test_can_detect_type_mismatch(self) -> None:
290 """An O payload wrapped as C must be detectable."""
291 from muse.core.types import blob_id
292
293 # payload says t="O" but we wrap it as frame_type="C"
294 payload = _pack({"t": "O", "id": blob_id(b"x"), "content": b"x", "enc": "raw"})
295 header = {"ft": "C", "id": blob_id(payload), "sz": len(payload)}
296 header_bytes = _pack(header)
297 wrapped = b"".join([
298 b"muse",
299 bytes([1]),
300 struct.pack(">I", len(header_bytes)),
301 header_bytes,
302 struct.pack(">Q", len(payload)),
303 payload,
304 ])
305 h, p = _decode_envelope(wrapped)
306 decoded = _unpack(p)
307 assert h["ft"] != decoded["t"], "mismatch should be detectable"
308
309
310 # ---------------------------------------------------------------------------
311 # I — Wall-5 regression: truncation never produces map32 garbage
312 # ---------------------------------------------------------------------------
313
314 class TestWall5Regression:
315 """I. Truncated C frame never produces map32/max_map_len garbage parsing."""
316
317 def _build_c_frame(self, n_commits: int = 200) -> bytes:
318 from muse.core.mpack import MuseWireFrameWriter
319 commits = [
320 {"commit_id": blob_id(f"commit-{i}".encode()), "message": f"msg {i}"}
321 for i in range(n_commits)
322 ]
323 payload = _pack({"t": "C", "commits": commits, "snapshots": []})
324 fw = MuseWireFrameWriter()
325 return fw.wrap(frame_type="C", payload=payload)
326
327 def test_truncation_at_magic_detected_cleanly(self) -> None:
328 """Truncating at byte 2 (mid-magic) should not produce map32 parse attempt."""
329 wrapped = self._build_c_frame()
330 truncated = wrapped[:2]
331 # Can't even read the magic — should be detectable as EOF/too-short
332 assert len(truncated) < 4
333
334 def test_truncation_mid_payload_gives_wrong_length(self) -> None:
335 """Truncating mid-payload: reader reads declared payload_len but gets fewer bytes."""
336 wrapped = self._build_c_frame()
337 # Trim last 1000 bytes (mid-payload)
338 truncated = wrapped[:-1000]
339
340 header_len = struct.unpack(">I", truncated[5:9])[0]
341 offset = 9 + header_len
342 declared_payload_len = struct.unpack(">Q", truncated[offset:offset + 8])[0]
343 available_payload = len(truncated) - offset - 8
344 # With old v1 framing this would cause msgpack to misparse — now it's just
345 # a clean length shortfall that a reader can detect deterministically
346 assert available_payload < declared_payload_len
347
348 def test_no_map32_from_truncation_at_various_offsets(self) -> None:
349 """Truncation at any offset never triggers map32 parse when handled correctly."""
350 wrapped = self._build_c_frame(n_commits=50)
351
352 offsets = [1, 4, 9, 50, 100, len(wrapped) // 2, len(wrapped) - 1]
353 for cut in offsets:
354 truncated = wrapped[:cut]
355 # The truncated bytes must not be parseable as a complete v1 msgpack
356 # that would produce a valid frame — this confirms there's no accidental
357 # msgpack self-delimiting parse of partial data
358 try:
359 u = msgpack.Unpacker(raw=False)
360 u.feed(truncated)
361 frames = list(u)
362 # If anything parsed, it should NOT be a valid v2 wire frame
363 # (i.e., we can't accidentally get a well-formed C frame from garbage)
364 for f in frames:
365 if isinstance(f, dict) and f.get("t") == "C":
366 commits = f.get("commits", [])
367 # If we parsed a C frame from truncated v2 bytes,
368 # it should not have all the original commits intact
369 assert len(commits) < 50, (
370 f"truncation at offset {cut} parsed a complete C frame — "
371 "this suggests the truncation point happened to preserve the entire payload"
372 )
373 except Exception:
374 # Any exception from msgpack is fine — that's the point
375 pass
File History 1 commit
sha256:51ce277f663e01a43eaffbe77509b1de7ac2d4251b55d23306304bcdeb92c90d feat(pack): delta-encode snapshots in MPackBundle wire format Sonnet 4.6 minor 121 days ago