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