<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Website Fingerprinting Lab</title> <style> * { margin: 0; padding: 0; } body { font-family: Arial, Helvetica, sans-serif; padding: 64px; } h1 { font-size: 32px; margin-bottom: 16px; } p { margin-bottom: 8px; } #buttons { margin-bottom: 16px; } button { background: #fff; border: 2px solid #3498db; border-radius: 4px; color: #3498db; cursor: pointer; display: inline-block; font-size: 16px; margin-right: 16px; padding: 8px 16px; } button.disabled { background: #ccc; border-color: #ccc; color: #666; cursor: default; } .trace { margin-bottom: 16px; } </style> </head> <body> <h1>Website Fingerprinting Lab</h1> <div id="buttons"> <button id="collect-trace">Collect trace</button> <button id="download-traces">Download traces</button> </div> <div id="traces"></div> <script src="https://d3js.org/d3.v6.js"></script> <script type="text/javascript"> const worker = new Worker("worker.js"); const collectTraceButton = document.getElementById("collect-trace"); const downloadTracesButton = document.getElementById("download-traces"); // Default values for when the automation script isn't being used. When // the script is in use, these values will get overwritten. window.trace_length = 5000; window.using_automation_script = false; window.recording = false; window.traces = []; let traceIds = []; worker.onmessage = (e) => { window.recording = false; const trace = JSON.parse(e.data); window.traces.push(trace); if (window.using_automation_script) { // Don't display traces when automation script is in use return; } // Create new trace div const parent = document.getElementById("traces"); const div = document.createElement("div"); const traceId = "a" + Math.random().toString().substring(2, 10); div.setAttribute("id", traceId); div.className = "trace"; parent.appendChild(div); traceIds.push(traceId); // Trace dimensions const width = parent.getBoundingClientRect().width; const height = 64; // Create div for new trace const svg = d3 .select("#" + traceId) .append("svg") .attr("width", width) .attr("height", height); // Find largest value across all traces const maxVal = d3.max(window.traces, (d) => d3.max(d)); for (let i = 0; i < window.traces.length; i++) { // Re-visualize all traces each time in case maxVal changes const x = d3 .scaleLinear() .domain([0, window.traces[i].length]) .range([0, width]); const color = d3 .scaleQuantize() .range(["#0d0887", "#7e03a8", "#cc4778", "#f89540", "#f0f921"]) .domain([0, maxVal]); svg .selectAll() .data(window.traces[i].map((x, i) => ({ index: i, value: x }))) .join("rect") .attr("x", (d) => x(d.index)) .attr("y", 0) .attr("width", x(1)) .attr("height", height) .style("fill", (d) => color(d.value)); } // Reset UI collectTraceButton.innerText = "Collect trace"; collectTraceButton.className = ""; }; function collectTrace() { collectTraceButton.innerText = "Collecting trace..."; collectTraceButton.className = "disabled"; window.recording = true; worker.postMessage({ type: "start", trace_length: window.trace_length, }); } collectTraceButton.onclick = () => { if (window.recording) return; window.recording = true; collectTraceButton.innerText = "Starting in 3..."; collectTraceButton.className = "disabled"; setTimeout(() => { collectTraceButton.innerText = "Starting in 2..."; setTimeout(() => { collectTraceButton.innerText = "Starting in 1..."; setTimeout(collectTrace, 1000); }, 1000); }, 1000); }; downloadTracesButton.onclick = () => { const blob = new Blob([JSON.stringify({ traces: window.traces })], { type: "application/json", }); const url = URL.createObjectURL(blob); const elem = document.createElement("a"); elem.href = url; elem.download = "traces.json"; document.body.appendChild(elem); elem.click(); document.body.removeChild(elem); }; </script> </body> </html>