test_wire_framing.py
python
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