from __future__ import annotations import hashlib import json from pathlib import Path import pytest from k1link.compute import prepare_camera_compute_job, validate_camera_compute_job from k1link.sessions import SessionIntegrityError, inspect_recorded_media_epoch from k1link.web.camera_archive import CameraArchiveWriter def _recorded_h264_fixture() -> tuple[bytes, bytes]: def box(box_type: bytes, payload: bytes = b"") -> bytes: return (8 + len(payload)).to_bytes(4, "big") + box_type + payload def full_box(box_type: bytes, payload: bytes = b"", *, flags: int = 0) -> bytes: return box(box_type, bytes([0]) + flags.to_bytes(3, "big") + payload) track_id = 1 sample_duration = 500 tkhd = full_box( b"tkhd", b"\x00" * 8 + track_id.to_bytes(4, "big") + b"\x00" * 4, ) mdhd = full_box( b"mdhd", b"\x00" * 8 + (1_000).to_bytes(4, "big") + b"\x00" * 4, ) hdlr = full_box(b"hdlr", b"\x00" * 4 + b"vide") trak = box(b"trak", tkhd + box(b"mdia", mdhd + hdlr)) trex = full_box( b"trex", track_id.to_bytes(4, "big") + (1).to_bytes(4, "big") + sample_duration.to_bytes(4, "big") + b"\x00" * 8, ) init = box(b"ftyp", b"isom") + box( b"moov", trak + box(b"mvex", trex) + box(b"avcC", b"\x01\x64\x00\x28"), ) tfhd = full_box(b"tfhd", track_id.to_bytes(4, "big"), flags=0x020000) tfdt = full_box(b"tfdt", (0).to_bytes(4, "big")) trun = full_box(b"trun", (1).to_bytes(4, "big")) fragment = box(b"moof", box(b"traf", tfhd + tfdt + trun)) + box(b"mdat", b"frame") return init, fragment def _camera_epoch(tmp_path: Path) -> tuple[Path, bytes]: session = tmp_path / "session-1" session.mkdir() init, fragment = _recorded_h264_fixture() writer = CameraArchiveWriter(session, "sensor.camera.left", 1) writer.append("init", init, host_epoch_ns=1_000_000_000, host_monotonic_ns=2_000_000_000) writer.append( "media", fragment, host_epoch_ns=1_500_000_000, host_monotonic_ns=2_500_000_000, ) writer.close() return session, fragment def test_camera_compute_job_is_content_addressed_and_idempotent(tmp_path: Path) -> None: session, fragment = _camera_epoch(tmp_path) source_epoch = session / "media" / "sensor.camera.left" / "epoch-1" source_identity = { path.relative_to(source_epoch).as_posix(): hashlib.sha256(path.read_bytes()).hexdigest() for path in source_epoch.rglob("*") if path.is_file() } job = prepare_camera_compute_job( session_root=session, source_id="sensor.camera.left", codec_epoch=1, origin_epoch_ns=1_000_000_000, origin_monotonic_ns=2_000_000_000, output_root=tmp_path / "jobs", ) repeated = prepare_camera_compute_job( session_root=session, source_id="sensor.camera.left", codec_epoch=1, origin_epoch_ns=1_000_000_000, origin_monotonic_ns=2_000_000_000, output_root=tmp_path / "jobs", ) assert repeated == job assert job.job_id == f"recorded-camera-{job.input_sha256[:24]}" assert job.segment_count == 1 assert job.timeline_start_seconds == 0.0 assert job.timeline_end_seconds == 0.5 copied = ( job.job_root / "input" / "camera" / "sensor.camera.left" / "epoch-1" / "segments" / "1.m4s" ) assert copied.read_bytes() == fragment assert validate_camera_compute_job(job.job_root) == job assert source_identity == { path.relative_to(source_epoch).as_posix(): hashlib.sha256(path.read_bytes()).hexdigest() for path in source_epoch.rglob("*") if path.is_file() } def test_camera_compute_job_rejects_changed_published_payload(tmp_path: Path) -> None: session, _ = _camera_epoch(tmp_path) job = prepare_camera_compute_job( session_root=session, source_id="sensor.camera.left", codec_epoch=1, origin_epoch_ns=1_000_000_000, origin_monotonic_ns=2_000_000_000, output_root=tmp_path / "jobs", ) segment = next(job.job_root.glob("input/camera/*/epoch-1/segments/1.m4s")) segment.write_bytes(b"changed") with pytest.raises(SessionIntegrityError, match="payload identity changed"): validate_camera_compute_job(job.job_root) def test_epoch_inspection_checks_summary_aggregate_digests(tmp_path: Path) -> None: session, _ = _camera_epoch(tmp_path) epoch = session / "media" / "sensor.camera.left" / "epoch-1" summary_path = epoch / "summary.json" summary = json.loads(summary_path.read_text(encoding="utf-8")) summary["index_sha256"] = "0" * 64 summary_path.write_text(json.dumps(summary), encoding="utf-8") with pytest.raises(SessionIntegrityError, match="index digest changed"): inspect_recorded_media_epoch( epoch, expected_source_name="sensor.camera.left", origin_epoch_ns=1_000_000_000, origin_monotonic_ns=2_000_000_000, )