from __future__ import annotations
import argparse
import re
import sys
from pathlib import Path
ALLOWLIST = Path("docs/sans-io-deterministic-time-allowlist.txt")
SOURCE_ROOTS = ["src"]
SOURCE_GLOB = "rtc-*/src"
CLOCK_READS = re.compile(
r"\bInstant::now\b"
r"|\bSystemTime::now\b"
r"|\bSystemInstant::now\(\)"
r"|\.elapsed\(\)"
)
TEST_FILE = re.compile(r"_tests?\.rs$|(^|/)tests?/")
TEST_ATTR = re.compile(r"^\s*#\[(?:cfg\(test\)|(?:[\w:]+::)?(?:test|bench))\]")
LINE_COMMENT = re.compile(r"^\s*(//|/\*|\*)")
def source_files() -> list[Path]:
roots = [Path(r) for r in SOURCE_ROOTS if Path(r).is_dir()]
roots += sorted(p for p in Path(".").glob(SOURCE_GLOB) if p.is_dir())
files: list[Path] = []
for root in roots:
for path in root.rglob("*.rs"):
if not TEST_FILE.search(path.as_posix()):
files.append(path)
return sorted(files)
def clock_reads(path: Path) -> list[tuple[int, str]]:
found: list[tuple[int, str]] = []
lines = path.read_text().splitlines()
skipping = False
pending = False depth = 0
for number, line in enumerate(lines, start=1):
if skipping:
depth += line.count("{") - line.count("}")
if depth <= 0:
skipping = False
continue
if pending:
opened = line.count("{")
if opened:
depth = opened - line.count("}")
if depth > 0:
skipping, pending = True, False
continue
pending = False
elif line.rstrip().endswith(";"):
pending = False
continue
if TEST_ATTR.match(line):
pending = True
continue
if LINE_COMMENT.match(line):
continue
if CLOCK_READS.search(line):
found.append((number, line.strip()))
return found
def scan() -> dict[str, list[tuple[int, str]]]:
return {
path.as_posix(): reads
for path in source_files()
if (reads := clock_reads(path))
}
def load_allowlist() -> dict[str, int]:
if not ALLOWLIST.exists():
return {}
allowed: dict[str, int] = {}
for raw in ALLOWLIST.read_text().splitlines():
line = raw.strip()
if not line or line.startswith("#"):
continue
parts = line.split(":", 2)
if len(parts) < 2:
print(f"Malformed allow-list line (want path:count:reason): {raw}")
sys.exit(2)
allowed[parts[0].strip()] = int(parts[1].strip())
return allowed
PERMANENT: dict[str, tuple[int, str]] = {
"rtc-shared/src/time.rs": (3, "SystemInstant's construction and its epoch anchor"),
"rtc-dtls/src/handshake/handshake_random.rs": (1, "gmt_unix_time, RFC 5246 s7.4.1.2"),
"rtc-sdp/src/description/session.rs": (1, "SDP session version, RFC 4566 s5.2"),
"src/peer_connection/certificate/mod.rs": (1, "X.509 validity window"),
"src/peer_connection/configuration/mod.rs": (1, "X.509 expiry check, W3C constructor step 3"),
"src/peer_connection/transport/dtls/mod.rs": (1, "X.509 validity window"),
}
HEADER = """\
# Ambient clock reads permitted in non-test sans-I/O code.
#
# Format: path:count:reason
#
# A per-file count rather than a line number, so that unrelated edits do not shift a pinned line
# and break CI during a migration that touches these files constantly. Regenerate with:
#
# python3 scripts/check-sans-io-deterministic-time.py --snapshot
#
# Only ever to record a REDUCTION. If a count went up, the new read is the bug — see
# docs/sans-io-deterministic-time.md.
"""
def write_snapshot(found: dict[str, list[tuple[int, str]]]) -> None:
permanent: list[str] = []
mixed: list[str] = []
temporary: list[str] = []
for path in sorted(found):
total = len(found[path])
keep, reason = PERMANENT.get(path, (0, ""))
if keep and total == keep:
permanent.append(f"{path}:{total}:{reason}")
elif keep:
mixed.append(
f"{path}:{total}:{keep} permanent ({reason}) + {total - keep} awaiting injection"
)
else:
temporary.append(f"{path}:{total}:awaiting clock injection")
out = [HEADER]
out.append("# --- Permanent: wall-clock is correct here -----------------------------------------")
out.append("# These stay after the migration completes. See docs/sans-io-deterministic-time.md for why each")
out.append("# category is a real-world observation rather than a protocol decision.")
out.append("")
out += permanent
out.append("")
out.append("# --- Mixed: a permanent read sharing a file with one that migrates -----------------")
out.append("")
out += mixed
out.append("")
out.append("# --- Temporary: awaiting migration -------------------------------------------------")
out.append("# Each phase deletes lines from here. See sans-io-deterministic-time-work-plan.md.")
out.append("# Target end state: this section is empty.")
out.append("")
out += temporary
ALLOWLIST.parent.mkdir(parents=True, exist_ok=True)
ALLOWLIST.write_text("\n".join(out) + "\n")
total = sum(len(r) for r in found.values())
keep = sum(min(len(found[p]), PERMANENT.get(p, (0, ""))[0]) for p in found)
print(f"Wrote {ALLOWLIST} - {len(found)} files, {total} reads")
print(f" permanent: {keep}")
print(f" to migrate: {total - keep}")
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--snapshot",
action="store_true",
help="rewrite the allow-list from the current tree (to record a reduction)",
)
args = parser.parse_args()
if not source_files():
print("No sans-I/O source found. Run from the workspace root, where src/ and rtc-*/ live.")
return 2
found = scan()
if args.snapshot:
write_snapshot(found)
return 0
allowed = load_allowlist()
new_reads: list[str] = []
stale: list[str] = []
for path, reads in sorted(found.items()):
budget = allowed.get(path, 0)
if len(reads) > budget:
new_reads.append(f"{path}: {len(reads)} clock reads, {budget} allowed")
for number, text in reads:
new_reads.append(f" {path}:{number} {text}")
elif len(reads) < budget:
stale.append(f"{path}: {len(reads)} clock reads, {budget} still allowed")
for path, budget in sorted(allowed.items()):
if path not in found and budget:
stale.append(f"{path}: no clock reads left, {budget} still allowed")
if new_reads:
print("Ambient clock reads in sans-I/O code that the allow-list does not permit:")
print()
for line in new_reads:
print(f" {line}")
print()
print("Sans-I/O protocol code is told the time; it does not ask. Take the instant from")
print("`handle_timeout(now)`, from `msg.now` / `evt.now` on the inbound payload, or from")
print("the instant retained from the last of those. Use `saturating_duration_since`")
print("rather than `.elapsed()`. See docs/sans-io-deterministic-time.md.")
return 1
if stale:
print("Clock reads were removed but the allow-list still budgets for them:")
print()
for line in stale:
print(f" {line}")
print()
print("Good news — record it: python3 scripts/check-sans-io-deterministic-time.py --snapshot")
return 1
total = sum(len(r) for r in found.values())
print(f"Sans-I/O deterministic-time boundary holds: {total} allow-listed reads, no new ones.")
return 0
if __name__ == "__main__":
sys.exit(main())