```{ojs}
//| echo: false
//| label: attack-animation-canvas
//| fig-alt: "Animated visualization showing the step-by-step progression of the selected security attack with attacker, victim, and server components"
// Main attack visualization canvas
attackVisualization = {
const width = 900;
const height = 500;
const attack = selectedAttack;
const step = currentStep;
const protected = showProtection;
const svg = d3.create("svg")
.attr("viewBox", `0 0 ${width} ${height}`)
.attr("width", "100%")
.attr("height", height)
.style("background", "linear-gradient(180deg, #1a1a2e 0%, #16213e 100%)")
.style("border-radius", "12px")
.style("box-shadow", "0 4px 20px rgba(0,0,0,0.3)");
// Add grid pattern for tech feel
const defs = svg.append("defs");
const gridPattern = defs.append("pattern")
.attr("id", "grid")
.attr("width", 40)
.attr("height", 40)
.attr("patternUnits", "userSpaceOnUse");
gridPattern.append("path")
.attr("d", "M 40 0 L 0 0 0 40")
.attr("fill", "none")
.attr("stroke", "rgba(255,255,255,0.03)")
.attr("stroke-width", 1);
svg.append("rect")
.attr("width", width)
.attr("height", height)
.attr("fill", "url(#grid)");
// Glow filter for active elements
const glowFilter = defs.append("filter")
.attr("id", "glow")
.attr("x", "-50%")
.attr("y", "-50%")
.attr("width", "200%")
.attr("height", "200%");
glowFilter.append("feGaussianBlur")
.attr("stdDeviation", "3")
.attr("result", "coloredBlur");
const glowMerge = glowFilter.append("feMerge");
glowMerge.append("feMergeNode").attr("in", "coloredBlur");
glowMerge.append("feMergeNode").attr("in", "SourceGraphic");
// Attack-specific entity positions
const entities = getEntityPositions(attack.id, width, height);
// Draw entities
entities.forEach((entity, i) => {
const group = svg.append("g")
.attr("transform", `translate(${entity.x}, ${entity.y})`);
// Entity shadow
group.append("ellipse")
.attr("cx", 0)
.attr("cy", 50)
.attr("rx", 40)
.attr("ry", 10)
.attr("fill", "rgba(0,0,0,0.3)");
// Entity body based on type
if (entity.type === "device") {
// IoT Device
group.append("rect")
.attr("x", -30)
.attr("y", -35)
.attr("width", 60)
.attr("height", 70)
.attr("rx", 8)
.attr("fill", entity.active && step >= entity.activeStep ? ieeeColors.orange : ieeeColors.teal)
.attr("stroke", ieeeColors.white)
.attr("stroke-width", 2)
.attr("filter", entity.active && step >= entity.activeStep ? "url(#glow)" : "none");
// Antenna
group.append("line")
.attr("x1", 0)
.attr("y1", -35)
.attr("x2", 0)
.attr("y2", -55)
.attr("stroke", ieeeColors.white)
.attr("stroke-width", 2);
group.append("circle")
.attr("cx", 0)
.attr("cy", -58)
.attr("r", 4)
.attr("fill", ieeeColors.green);
// Screen
group.append("rect")
.attr("x", -20)
.attr("y", -25)
.attr("width", 40)
.attr("height", 25)
.attr("rx", 3)
.attr("fill", "#0f1923");
} else if (entity.type === "attacker") {
// Attacker (hooded figure)
group.append("circle")
.attr("cx", 0)
.attr("cy", -20)
.attr("r", 25)
.attr("fill", ieeeColors.red)
.attr("filter", step >= entity.activeStep ? "url(#glow)" : "none");
// Hood
group.append("path")
.attr("d", "M -25 -10 Q -30 -45 0 -50 Q 30 -45 25 -10")
.attr("fill", ieeeColors.red)
.attr("stroke", "#a93226")
.attr("stroke-width", 2);
// Mask eyes
group.append("rect")
.attr("x", -15)
.attr("y", -25)
.attr("width", 30)
.attr("height", 8)
.attr("rx", 4)
.attr("fill", "#0f1923");
group.append("circle").attr("cx", -8).attr("cy", -21).attr("r", 2).attr("fill", ieeeColors.orange);
group.append("circle").attr("cx", 8).attr("cy", -21).attr("r", 2).attr("fill", ieeeColors.orange);
// Body
group.append("path")
.attr("d", "M -20 5 L -30 45 L 30 45 L 20 5 Z")
.attr("fill", ieeeColors.red)
.attr("stroke", "#a93226")
.attr("stroke-width", 2);
} else if (entity.type === "server") {
// Server
group.append("rect")
.attr("x", -35)
.attr("y", -45)
.attr("width", 70)
.attr("height", 90)
.attr("rx", 5)
.attr("fill", entity.active && step >= entity.activeStep ? ieeeColors.orange : ieeeColors.navy)
.attr("stroke", ieeeColors.white)
.attr("stroke-width", 2)
.attr("filter", entity.active && step >= entity.activeStep ? "url(#glow)" : "none");
// Server slots
for (let j = 0; j < 4; j++) {
group.append("rect")
.attr("x", -28)
.attr("y", -38 + j * 20)
.attr("width", 56)
.attr("height", 14)
.attr("rx", 2)
.attr("fill", "#0f1923");
// Blinking lights
for (let k = 0; k < 3; k++) {
group.append("circle")
.attr("cx", -22 + k * 8)
.attr("cy", -31 + j * 20)
.attr("r", 2)
.attr("fill", j === step % 4 ? ieeeColors.green : ieeeColors.gray);
}
}
} else if (entity.type === "shield") {
// Protection shield (only shown in protected mode)
if (protected) {
group.append("path")
.attr("d", "M 0 -50 L 35 -35 L 35 15 Q 35 45 0 55 Q -35 45 -35 15 L -35 -35 Z")
.attr("fill", `${ieeeColors.green}40`)
.attr("stroke", ieeeColors.green)
.attr("stroke-width", 3)
.attr("filter", "url(#glow)");
group.append("text")
.attr("x", 0)
.attr("y", 5)
.attr("text-anchor", "middle")
.attr("fill", ieeeColors.green)
.attr("font-size", "24px")
.text("🛡️");
}
}
// Entity label
group.append("text")
.attr("x", 0)
.attr("y", 70)
.attr("text-anchor", "middle")
.attr("fill", ieeeColors.white)
.attr("font-size", "14px")
.attr("font-weight", "600")
.text(entity.label);
});
// Draw attack flow arrows based on current step
const flows = getAttackFlows(attack.id, step, protected, entities, width, height);
flows.forEach((flow, i) => {
if (flow.visible) {
const path = svg.append("path")
.attr("d", flow.path)
.attr("fill", "none")
.attr("stroke", flow.blocked ? ieeeColors.red : flow.malicious ? ieeeColors.orange : ieeeColors.teal)
.attr("stroke-width", flow.blocked ? 4 : 3)
.attr("stroke-dasharray", flow.malicious ? "10,5" : flow.blocked ? "5,5" : "none")
.attr("marker-end", flow.blocked ? "none" : `url(#arrow-${flow.malicious ? 'orange' : 'teal'})`);
// Animate packets along path
if (flow.animated && !flow.blocked) {
const pathLength = path.node().getTotalLength();
for (let p = 0; p < 3; p++) {
const packet = svg.append("circle")
.attr("r", 6)
.attr("fill", flow.malicious ? ieeeColors.orange : ieeeColors.teal)
.attr("filter", "url(#glow)");
function animatePacket() {
packet
.attr("transform", `translate(${path.node().getPointAtLength(0).x}, ${path.node().getPointAtLength(0).y})`)
.transition()
.duration(2000 / animationSpeed)
.delay(p * 600)
.ease(d3.easeLinear)
.attrTween("transform", function() {
return function(t) {
const point = path.node().getPointAtLength(t * pathLength);
return `translate(${point.x}, ${point.y})`;
};
})
.on("end", animatePacket);
}
animatePacket();
}
}
// Flow label
if (flow.label) {
const midPoint = path.node().getPointAtLength(path.node().getTotalLength() / 2);
svg.append("text")
.attr("x", midPoint.x)
.attr("y", midPoint.y - 15)
.attr("text-anchor", "middle")
.attr("fill", flow.malicious ? ieeeColors.orange : ieeeColors.lightGray)
.attr("font-size", "11px")
.attr("font-weight", "500")
.text(flow.label);
}
// Blocked X mark
if (flow.blocked) {
const midPoint = path.node().getPointAtLength(path.node().getTotalLength() / 2);
svg.append("text")
.attr("x", midPoint.x)
.attr("y", midPoint.y + 5)
.attr("text-anchor", "middle")
.attr("fill", ieeeColors.red)
.attr("font-size", "30px")
.attr("font-weight", "bold")
.text("✕");
}
}
});
// Arrow markers
const arrowColors = [
{ id: "teal", color: ieeeColors.teal },
{ id: "orange", color: ieeeColors.orange }
];
arrowColors.forEach(ac => {
defs.append("marker")
.attr("id", `arrow-${ac.id}`)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 8)
.attr("refY", 0)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5")
.attr("fill", ac.color);
});
// Phase indicator
const phaseGroup = svg.append("g")
.attr("transform", `translate(${width/2}, 25)`);
phaseGroup.append("text")
.attr("text-anchor", "middle")
.attr("fill", ieeeColors.orange)
.attr("font-size", "18px")
.attr("font-weight", "bold")
.text(`Phase ${step + 1}: ${attack.steps[step].phase}`);
// Step description
svg.append("foreignObject")
.attr("x", 20)
.attr("y", height - 90)
.attr("width", width - 40)
.attr("height", 80)
.append("xhtml:div")
.style("background", "rgba(0,0,0,0.7)")
.style("padding", "12px 16px")
.style("border-radius", "8px")
.style("border-left", `4px solid ${ieeeColors.orange}`)
.html(`
<div style="color: ${ieeeColors.white}; font-size: 14px; line-height: 1.5;">
<strong>${attack.steps[step].description}</strong>
${protected ? `<br><span style="color: ${ieeeColors.green};">🛡️ Protection: Attack mitigated by security controls</span>` : ''}
</div>
`);
return svg.node();
}
// Helper function for entity positions
function getEntityPositions(attackId, width, height) {
const baseEntities = {
mitm: [
{ type: "device", label: "IoT Device", x: 150, y: height/2 - 30, active: true, activeStep: 2 },
{ type: "attacker", label: "Attacker", x: width/2, y: height/2 - 30, active: true, activeStep: 1 },
{ type: "server", label: "Cloud Server", x: width - 150, y: height/2 - 30, active: true, activeStep: 2 },
{ type: "shield", label: "", x: width/2, y: height/2 - 30, active: true, activeStep: 0 }
],
replay: [
{ type: "device", label: "Victim Device", x: 150, y: height/2 - 30, active: true, activeStep: 0 },
{ type: "attacker", label: "Attacker", x: width/2, y: height/2 + 80, active: true, activeStep: 1 },
{ type: "server", label: "Target Server", x: width - 150, y: height/2 - 30, active: true, activeStep: 3 },
{ type: "shield", label: "", x: width - 150, y: height/2 - 30, active: true, activeStep: 0 }
],
dos: [
{ type: "device", label: "Botnet Device 1", x: 100, y: 120, active: true, activeStep: 2 },
{ type: "device", label: "Botnet Device 2", x: 100, y: height/2, active: true, activeStep: 2 },
{ type: "device", label: "Botnet Device 3", x: 100, y: height - 140, active: true, activeStep: 2 },
{ type: "attacker", label: "C&C Server", x: width/2, y: height/2, active: true, activeStep: 1 },
{ type: "server", label: "Target Server", x: width - 120, y: height/2, active: true, activeStep: 3 },
{ type: "shield", label: "", x: width - 120, y: height/2, active: true, activeStep: 0 }
],
sidechannel: [
{ type: "device", label: "Target Device", x: width/2, y: height/2 - 30, active: true, activeStep: 1 },
{ type: "attacker", label: "Attacker + Probe", x: width/2 + 180, y: height/2 - 30, active: true, activeStep: 1 },
{ type: "shield", label: "", x: width/2, y: height/2 - 30, active: true, activeStep: 0 }
],
firmware: [
{ type: "device", label: "Target Device", x: width/2, y: 150, active: true, activeStep: 0 },
{ type: "attacker", label: "Attacker", x: width/2, y: height - 150, active: true, activeStep: 1 },
{ type: "shield", label: "", x: width/2, y: 150, active: true, activeStep: 0 }
],
downgrade: [
{ type: "device", label: "IoT Client", x: 150, y: height/2 - 30, active: true, activeStep: 0 },
{ type: "attacker", label: "Attacker", x: width/2, y: height/2 - 30, active: true, activeStep: 1 },
{ type: "server", label: "Server", x: width - 150, y: height/2 - 30, active: true, activeStep: 0 },
{ type: "shield", label: "", x: width/2, y: height/2 - 30, active: true, activeStep: 0 }
]
};
return baseEntities[attackId] || baseEntities.mitm;
}
// Helper function for attack flow visualization
function getAttackFlows(attackId, step, protected, entities, width, height) {
const flows = {
mitm: [
{ visible: step >= 0, path: `M 180 ${height/2-30} Q 300 ${height/2-100} ${width/2-30} ${height/2-30}`, label: "Normal Traffic", animated: true, malicious: false, blocked: false },
{ visible: step >= 1, path: `M ${width/2-30} ${height/2-30} Q 300 ${height/2+50} 180 ${height/2-30}`, label: "ARP Poison", animated: true, malicious: true, blocked: protected },
{ visible: step >= 2, path: `M ${width/2+30} ${height/2-30} Q 650 ${height/2-100} ${width-180} ${height/2-30}`, label: "Forwarded (intercepted)", animated: true, malicious: step >= 3, blocked: false },
{ visible: step >= 3, path: `M ${width/2} ${height/2+20} L ${width/2} ${height/2+80}`, label: "Extracted Data", animated: false, malicious: true, blocked: protected },
{ visible: step >= 4, path: `M ${width/2+30} ${height/2-30} Q 650 ${height/2+50} ${width-180} ${height/2-30}`, label: "Modified Data", animated: true, malicious: true, blocked: protected }
],
replay: [
{ visible: step >= 0, path: `M 180 ${height/2-30} Q ${width/2} ${height/2-100} ${width-180} ${height/2-30}`, label: "Legitimate Message", animated: true, malicious: false, blocked: false },
{ visible: step >= 1, path: `M ${width/2} ${height/2-80} L ${width/2} ${height/2+30}`, label: "Capture", animated: false, malicious: true, blocked: false },
{ visible: step >= 2, path: `M ${width/2+30} ${height/2+80} L ${width/2+80} ${height/2+80}`, label: "Store", animated: false, malicious: true, blocked: false },
{ visible: step >= 3, path: `M ${width/2+30} ${height/2+80} Q ${width-200} ${height/2+60} ${width-180} ${height/2-30}`, label: "Replay", animated: true, malicious: true, blocked: protected },
{ visible: step >= 4, path: `M ${width-150} ${height/2+20} L ${width-150} ${height/2+60}`, label: protected ? "Rejected (nonce)" : "Accepted!", animated: false, malicious: !protected, blocked: false }
],
dos: [
{ visible: step >= 2, path: `M 130 120 Q ${width/2+100} 100 ${width-150} ${height/2-50}`, label: "Flood", animated: true, malicious: true, blocked: protected },
{ visible: step >= 2, path: `M 130 ${height/2} L ${width-150} ${height/2}`, label: "Flood", animated: true, malicious: true, blocked: protected },
{ visible: step >= 2, path: `M 130 ${height-140} Q ${width/2+100} ${height-80} ${width-150} ${height/2+50}`, label: "Flood", animated: true, malicious: true, blocked: protected },
{ visible: step >= 1, path: `M ${width/2-30} ${height/2} L 130 ${height/2}`, label: "C&C", animated: true, malicious: true, blocked: false },
{ visible: step >= 1, path: `M ${width/2-30} ${height/2-30} Q 200 100 130 120`, label: "", animated: true, malicious: true, blocked: false },
{ visible: step >= 1, path: `M ${width/2-30} ${height/2+30} Q 200 ${height-100} 130 ${height-140}`, label: "", animated: true, malicious: true, blocked: false }
],
sidechannel: [
{ visible: step >= 1, path: `M ${width/2+50} ${height/2-50} C ${width/2+100} ${height/2-100} ${width/2+150} ${height/2-80} ${width/2+150} ${height/2-50}`, label: "EM Emissions", animated: true, malicious: false, blocked: false },
{ visible: step >= 2, path: `M ${width/2+30} ${height/2} L ${width/2+150} ${height/2}`, label: "Power Trace", animated: true, malicious: false, blocked: protected },
{ visible: step >= 3, path: `M ${width/2+30} ${height/2+30} C ${width/2+100} ${height/2+80} ${width/2+150} ${height/2+60} ${width/2+150} ${height/2+30}`, label: "Timing Data", animated: true, malicious: false, blocked: protected },
{ visible: step >= 4, path: `M ${width/2+210} ${height/2-30} L ${width/2+280} ${height/2-30}`, label: protected ? "Noise Mask" : "Key Extracted!", animated: false, malicious: !protected, blocked: false }
],
firmware: [
{ visible: step >= 1, path: `M ${width/2} 200 L ${width/2} ${height-200}`, label: "UART/JTAG Access", animated: false, malicious: true, blocked: protected },
{ visible: step >= 2, path: `M ${width/2-40} 170 Q ${width/2-100} ${height/2} ${width/2-40} ${height-170}`, label: "Memory Dump", animated: true, malicious: true, blocked: protected },
{ visible: step >= 3, path: `M ${width/2+40} ${height-170} Q ${width/2+100} ${height/2} ${width/2+40} 170`, label: "Analysis", animated: true, malicious: true, blocked: false },
{ visible: step >= 4, path: `M ${width/2} ${height-100} L ${width/2} ${height-60}`, label: protected ? "Encrypted" : "Secrets Found!", animated: false, malicious: !protected, blocked: false }
],
downgrade: [
{ visible: step >= 0, path: `M 180 ${height/2-50} Q ${width/2} ${height/2-120} ${width-180} ${height/2-50}`, label: "TLS 1.3 Hello", animated: true, malicious: false, blocked: false },
{ visible: step >= 1, path: `M ${width-180} ${height/2-30} Q ${width/2} ${height/2-80} ${width/2-30} ${height/2-30}`, label: "Intercept", animated: true, malicious: true, blocked: false },
{ visible: step >= 2, path: `M ${width/2+30} ${height/2-30} Q ${width-200} ${height/2-80} ${width-180} ${height/2-30}`, label: protected ? "TLS 1.3 (enforced)" : "SSL 3.0 (forced)", animated: true, malicious: !protected, blocked: protected },
{ visible: step >= 3, path: `M ${width/2} ${height/2+20} L ${width/2} ${height/2+80}`, label: protected ? "Blocked" : "Exploit Vuln", animated: false, malicious: true, blocked: protected },
{ visible: step >= 4, path: `M ${width/2-30} ${height/2+80} Q 300 ${height/2+100} 180 ${height/2+30}`, label: protected ? "" : "Decrypted!", animated: !protected, malicious: true, blocked: protected }
]
};
return flows[attackId] || flows.mitm;
}
```