#!/usr/bin/env bash set -euo pipefail usage() { cat <<'USAGE' Usage: ./scripts/periodic/compare_ccr_cir_round.sh \ --ours-ccr \ --rpki-client-ccr \ --out-dir \ [--ours-cir ] \ [--rpki-client-cir ] \ [--trust-anchor ] \ [--sample-limit ] \ [--always-compare-cir] USAGE } ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" OURS_CCR="" CLIENT_CCR="" OURS_CIR="" CLIENT_CIR="" OUT_DIR="" TRUST_ANCHOR="unknown" SAMPLE_LIMIT="20" ALWAYS_COMPARE_CIR=0 while [[ $# -gt 0 ]]; do case "$1" in --ours-ccr) OURS_CCR="$2"; shift 2 ;; --rpki-client-ccr) CLIENT_CCR="$2"; shift 2 ;; --ours-cir) OURS_CIR="$2"; shift 2 ;; --rpki-client-cir) CLIENT_CIR="$2"; shift 2 ;; --out-dir) OUT_DIR="$2"; shift 2 ;; --trust-anchor) TRUST_ANCHOR="$2"; shift 2 ;; --sample-limit) SAMPLE_LIMIT="$2"; shift 2 ;; --always-compare-cir) ALWAYS_COMPARE_CIR=1; shift ;; -h|--help) usage; exit 0 ;; *) echo "unknown argument: $1" >&2; usage; exit 2 ;; esac done [[ -n "$OURS_CCR" && -n "$CLIENT_CCR" && -n "$OUT_DIR" ]] || { usage >&2; exit 2; } if [[ -n "$OURS_CIR" || -n "$CLIENT_CIR" ]]; then [[ -n "$OURS_CIR" && -n "$CLIENT_CIR" ]] || { echo "--ours-cir and --rpki-client-cir must be provided together" >&2; exit 2; } fi mkdir -p "$OUT_DIR/ccr" "$ROOT_DIR/scripts/periodic/compare_ccr_round.sh" \ --ours-ccr "$OURS_CCR" \ --rpki-client-ccr "$CLIENT_CCR" \ --out-dir "$OUT_DIR/ccr" \ --trust-anchor "$TRUST_ANCHOR" >/dev/null RUN_CIR="$(python3 - <<'PY' "$OUT_DIR/ccr/compare-summary.json" "$ALWAYS_COMPARE_CIR" "$OURS_CIR" "$CLIENT_CIR" import json import sys summary = json.load(open(sys.argv[1], "r", encoding="utf-8")) always = sys.argv[2] == "1" has_cir = bool(sys.argv[3]) and bool(sys.argv[4]) ccr_match = summary.get("stateDigestMatch") is True or summary.get("allMatch") is True print("1" if has_cir and (always or not ccr_match) else "0") PY )" if [[ "$RUN_CIR" == "1" ]]; then mkdir -p "$OUT_DIR/cir" "$ROOT_DIR/scripts/periodic/compare_cir_round.sh" \ --ours-cir "$OURS_CIR" \ --rpki-client-cir "$CLIENT_CIR" \ --out-dir "$OUT_DIR/cir" \ --sample-limit "$SAMPLE_LIMIT" >/dev/null fi python3 - <<'PY' \ "$OUT_DIR/ccr/compare-summary.json" \ "$OUT_DIR/cir/cir-compare-summary.json" \ "$OUT_DIR/summary.json" \ "$OUT_DIR/summary.md" \ "$ALWAYS_COMPARE_CIR" \ "$OURS_CIR" \ "$CLIENT_CIR" \ "$TRUST_ANCHOR" \ "$SAMPLE_LIMIT" import json import sys from pathlib import Path ccr_path = Path(sys.argv[1]) cir_path = Path(sys.argv[2]) summary_json = Path(sys.argv[3]) summary_md = Path(sys.argv[4]) always_compare_cir = sys.argv[5] == "1" ours_cir = sys.argv[6] client_cir = sys.argv[7] trust_anchor = sys.argv[8] sample_limit = int(sys.argv[9]) ccr = json.load(open(ccr_path, "r", encoding="utf-8")) ccr_match = ccr.get("stateDigestMatch") is True or ccr.get("allMatch") is True cir_available = bool(ours_cir) and bool(client_cir) cir_compared = cir_path.exists() cir = json.load(open(cir_path, "r", encoding="utf-8")) if cir_compared else None if ccr_match: if cir_compared and cir and cir.get("allMatch") is not True: diagnosis = "ccr_state_same_but_cir_process_diff" else: diagnosis = "ccr_state_digest_match" else: if not cir_available: diagnosis = "ccr_mismatch_cir_not_available" elif not cir_compared: diagnosis = "ccr_mismatch_cir_compare_skipped" elif cir.get("tals", {}).get("match") is not True: diagnosis = "tal_input_difference" elif cir.get("objects", {}).get("match") is not True: diagnosis = "input_object_or_manifest_accepted_set_difference" elif ( cir.get("rejects", {}).get("match") is not True or cir.get("rejectListSha256Match") is not True ): diagnosis = "validation_reject_policy_difference" else: diagnosis = "ccr_projection_sorting_encoding_or_non_cir_state_difference" combined = { "allMatch": bool(ccr_match and (not cir_compared or (cir and cir.get("allMatch") is True))), "diagnosis": diagnosis, "comparePath": ccr.get("comparePath"), "stateDigestMatch": ccr_match, "mismatchedStates": ccr.get("mismatchedStates", []), "mismatchedComponents": ccr.get("mismatchedComponents", []), "vrps": ccr.get("vrps"), "vaps": ccr.get("vaps"), "trustAnchor": trust_anchor, "sampleLimit": sample_limit, "ccr": { "summaryPath": str(ccr_path), "stateDigestMatch": ccr_match, "comparePath": ccr.get("comparePath"), "mismatchedStates": ccr.get("mismatchedStates", []), "mismatchedComponents": ccr.get("mismatchedComponents", []), "vrpMatch": ccr.get("vrps", {}).get("match"), "vapMatch": ccr.get("vaps", {}).get("match"), }, "cir": { "available": cir_available, "compared": cir_compared, "summaryPath": str(cir_path) if cir_compared else None, "comparePolicy": "always" if always_compare_cir else "on_ccr_mismatch", "allMatch": cir.get("allMatch") if cir else None, "objectsMatch": cir.get("objects", {}).get("match") if cir else None, "rejectsMatch": cir.get("rejects", {}).get("match") if cir else None, "talsMatch": cir.get("tals", {}).get("match") if cir else None, "rejectListSha256Match": cir.get("rejectListSha256Match") if cir else None, "oursObjectCount": cir.get("ours", {}).get("objectCount") if cir else None, "rpkiClientObjectCount": cir.get("rpkiClient", {}).get("objectCount") if cir else None, "oursRejectCount": cir.get("ours", {}).get("rejectCount") if cir else None, "rpkiClientRejectCount": cir.get("rpkiClient", {}).get("rejectCount") if cir else None, }, } summary_json.write_text(json.dumps(combined, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") lines = [ "# CCR/CIR Compare Summary", "", f"- `allMatch`: `{str(combined['allMatch']).lower()}`", f"- `diagnosis`: `{combined['diagnosis']}`", f"- `ccrStateDigestMatch`: `{str(ccr_match).lower()}`", f"- `ccrMismatchedStates`: `{','.join(combined['ccr']['mismatchedStates'])}`", f"- `cirCompared`: `{str(cir_compared).lower()}`", ] if cir: lines.extend([ f"- `cirObjectsMatch`: `{str(combined['cir']['objectsMatch']).lower()}`", f"- `cirRejectsMatch`: `{str(combined['cir']['rejectsMatch']).lower()}`", f"- `cirTalsMatch`: `{str(combined['cir']['talsMatch']).lower()}`", f"- `cirCounts`: ours objects `{combined['cir']['oursObjectCount']}`, rpki-client objects `{combined['cir']['rpkiClientObjectCount']}`, ours rejects `{combined['cir']['oursRejectCount']}`, rpki-client rejects `{combined['cir']['rpkiClientRejectCount']}`", ]) else: lines.append(f"- `cirSkippedReason`: `{'ccr matched' if ccr_match and not always_compare_cir else 'CIR inputs unavailable'}`") summary_md.write_text("\n".join(lines) + "\n", encoding="utf-8") print(summary_json) PY