1475  Device Provisioning Flow Visualizer

Interactive IoT Device Onboarding and Provisioning Methods

1475.1 Learning Objectives

After completing this section, you will be able to:

  1. Visualize step-by-step provisioning flows for different IoT onboarding methods
  2. Understand the actors, credentials, and security properties of each provisioning step
  3. Compare provisioning methods side-by-side using interactive controls
  4. Identify secure vs insecure channel transitions during device onboarding

1475.2 Overview

This interactive tool visualizes different IoT device provisioning and onboarding methods, showing step-by-step animated flows, security credentials exchanged, and trust establishment processes.

NoteWhat is Device Provisioning?

Device provisioning is the process of securely onboarding IoT devices to a network or cloud platform. It establishes device identity, configures security credentials, and enables secure communication. Different methods offer varying levels of security, complexity, and user friction.

Think of device provisioning like getting a new employee set up at a company:

  1. Identity Verification - Proving who they are (like showing ID)
  2. Access Credentials - Getting keys, badges, passwords
  3. Configuration - Setting up their computer and email
  4. Registration - Adding them to company systems

For IoT devices, this happens automatically or with minimal user interaction. The goal is to securely connect the device while preventing unauthorized devices from joining.

1475.3 Device Provisioning Flow Visualizer

```{ojs}
//| echo: false
//| label: device-provisioning-visualizer
//| fig-alt: "Interactive device provisioning flow visualizer showing different IoT onboarding methods with animated swimlane diagrams, security analysis, and method comparison"

// IEEE Color Palette
ieeeColors = ({
  navy: "#2C3E50",
  teal: "#16A085",
  orange: "#E67E22",
  gray: "#7F8C8D",
  lightGray: "#ECF0F1",
  darkGray: "#34495E",
  green: "#27AE60",
  red: "#E74C3C",
  purple: "#9B59B6",
  blue: "#3498DB",
  yellow: "#F1C40F"
})

// Provisioning Methods Configuration
provisioningMethods = [
  {
    id: "ztp",
    name: "Zero-Touch Provisioning (ZTP)",
    shortName: "ZTP",
    description: "Fully automated provisioning without user interaction",
    complexity: 2,
    security: 4,
    friction: 1,
    scalability: 5,
    actors: ["Device", "DHCP/DNS", "Provisioning Server", "Cloud Platform"],
    useCases: ["Enterprise deployments", "Large-scale rollouts", "Industrial IoT"],
    standards: ["RFC 8572", "SZTP", "Cisco ZTP"],
    steps: [
      {
        id: 1,
        name: "Factory Configuration",
        from: "manufacturer",
        to: "device",
        duration: "Offline",
        description: "Device manufactured with bootstrap credentials and discovery URLs",
        credentials: ["Bootstrap Certificate", "Vendor Root CA"],
        secure: true,
        attackSurface: "Supply chain compromise",
        icon: "factory"
      },
      {
        id: 2,
        name: "Device Powers On",
        from: "device",
        to: "device",
        duration: "< 1 sec",
        description: "Device boots and enters discovery mode",
        credentials: [],
        secure: true,
        attackSurface: "Physical tampering",
        icon: "power"
      },
      {
        id: 3,
        name: "DHCP/DNS Discovery",
        from: "device",
        to: "network",
        duration: "1-5 sec",
        description: "Device queries DHCP option 143 or DNS SRV records for provisioning server",
        credentials: [],
        secure: false,
        attackSurface: "DNS spoofing, DHCP hijacking",
        icon: "search"
      },
      {
        id: 4,
        name: "TLS Connection",
        from: "device",
        to: "server",
        duration: "2-5 sec",
        description: "Device connects to provisioning server using bootstrap credentials",
        credentials: ["Bootstrap Certificate", "Server Certificate"],
        secure: true,
        attackSurface: "Certificate validation bypass",
        icon: "lock"
      },
      {
        id: 5,
        name: "Device Authentication",
        from: "server",
        to: "device",
        duration: "1-3 sec",
        description: "Server validates device identity using serial number and bootstrap cert",
        credentials: ["Device Serial", "Bootstrap Certificate"],
        secure: true,
        attackSurface: "Credential theft",
        icon: "verify"
      },
      {
        id: 6,
        name: "Configuration Download",
        from: "server",
        to: "device",
        duration: "5-30 sec",
        description: "Device receives operational certificates, configuration, and firmware",
        credentials: ["Operational Certificate", "Cloud Credentials", "Config File"],
        secure: true,
        attackSurface: "Configuration injection",
        icon: "download"
      },
      {
        id: 7,
        name: "Cloud Registration",
        from: "device",
        to: "cloud",
        duration: "2-5 sec",
        description: "Device registers with cloud platform using operational credentials",
        credentials: ["Operational Certificate", "Device Token"],
        secure: true,
        attackSurface: "Registration hijacking",
        icon: "cloud"
      },
      {
        id: 8,
        name: "Activation Complete",
        from: "cloud",
        to: "device",
        duration: "< 1 sec",
        description: "Device activated and begins normal operation",
        credentials: ["Session Keys"],
        secure: true,
        attackSurface: "Session hijacking",
        icon: "check"
      }
    ]
  },
  {
    id: "qrcode",
    name: "QR Code / App-based",
    shortName: "QR/App",
    description: "User-assisted provisioning using mobile app and QR code",
    complexity: 3,
    security: 3,
    friction: 3,
    scalability: 3,
    actors: ["Device", "Mobile App", "User", "Cloud Platform"],
    useCases: ["Consumer IoT", "Smart home", "Retail products"],
    standards: ["Matter", "HomeKit", "SmartThings"],
    steps: [
      {
        id: 1,
        name: "Device Setup Mode",
        from: "device",
        to: "device",
        duration: "User action",
        description: "User powers on device and initiates pairing mode (button press)",
        credentials: [],
        secure: true,
        attackSurface: "Unauthorized pairing window",
        icon: "power"
      },
      {
        id: 2,
        name: "QR Code Scan",
        from: "user",
        to: "app",
        duration: "2-5 sec",
        description: "User scans QR code on device containing setup payload",
        credentials: ["Setup Code", "Device ID", "Vendor ID"],
        secure: true,
        attackSurface: "QR code cloning",
        icon: "qrcode"
      },
      {
        id: 3,
        name: "BLE/Wi-Fi Discovery",
        from: "app",
        to: "device",
        duration: "3-10 sec",
        description: "App discovers device via BLE or soft-AP Wi-Fi",
        credentials: ["Discriminator"],
        secure: false,
        attackSurface: "Nearby attacker interception",
        icon: "wifi"
      },
      {
        id: 4,
        name: "PASE Session",
        from: "app",
        to: "device",
        duration: "2-5 sec",
        description: "Password-Authenticated Session Establishment using setup code",
        credentials: ["Setup Code", "PASE Keys"],
        secure: true,
        attackSurface: "Brute force (mitigated by code entropy)",
        icon: "key"
      },
      {
        id: 5,
        name: "Network Credentials",
        from: "app",
        to: "device",
        duration: "1-3 sec",
        description: "App sends Wi-Fi/Thread credentials to device",
        credentials: ["Wi-Fi SSID/Password", "Thread Network Key"],
        secure: true,
        attackSurface: "Credential extraction from app",
        icon: "network"
      },
      {
        id: 6,
        name: "Operational Certificate",
        from: "app",
        to: "device",
        duration: "3-10 sec",
        description: "App generates and installs operational certificate (NOC)",
        credentials: ["Node Operational Certificate", "Fabric Credentials"],
        secure: true,
        attackSurface: "Certificate injection",
        icon: "certificate"
      },
      {
        id: 7,
        name: "Cloud Registration",
        from: "app",
        to: "cloud",
        duration: "2-5 sec",
        description: "App registers device with cloud platform",
        credentials: ["Device ID", "User Token"],
        secure: true,
        attackSurface: "Account takeover",
        icon: "cloud"
      },
      {
        id: 8,
        name: "Setup Complete",
        from: "cloud",
        to: "device",
        duration: "< 1 sec",
        description: "Device paired and operational",
        credentials: ["Session Keys"],
        secure: true,
        attackSurface: "None (secured)",
        icon: "check"
      }
    ]
  },
  {
    id: "x509",
    name: "Certificate-based (X.509)",
    shortName: "X.509",
    description: "Mutual TLS authentication using manufacturer certificates",
    complexity: 4,
    security: 5,
    friction: 1,
    scalability: 5,
    actors: ["Device", "Manufacturer CA", "Cloud Platform", "Registration Authority"],
    useCases: ["Critical infrastructure", "Healthcare", "Financial IoT"],
    standards: ["X.509", "IEEE 802.1AR", "IDevID"],
    steps: [
      {
        id: 1,
        name: "Certificate Embedding",
        from: "manufacturer",
        to: "device",
        duration: "Factory",
        description: "Manufacturer embeds unique device certificate (IDevID) in secure element",
        credentials: ["Device Certificate", "Private Key", "CA Chain"],
        secure: true,
        attackSurface: "Manufacturing compromise",
        icon: "factory"
      },
      {
        id: 2,
        name: "Device Powers On",
        from: "device",
        to: "device",
        duration: "< 1 sec",
        description: "Device boots and loads certificate from secure element",
        credentials: ["Device Certificate"],
        secure: true,
        attackSurface: "Secure element extraction",
        icon: "power"
      },
      {
        id: 3,
        name: "DNS Resolution",
        from: "device",
        to: "network",
        duration: "1-3 sec",
        description: "Device resolves cloud endpoint from hardcoded or configured hostname",
        credentials: [],
        secure: false,
        attackSurface: "DNS hijacking",
        icon: "search"
      },
      {
        id: 4,
        name: "TLS Client Hello",
        from: "device",
        to: "cloud",
        duration: "< 1 sec",
        description: "Device initiates TLS handshake with client certificate",
        credentials: ["Device Certificate"],
        secure: true,
        attackSurface: "Downgrade attack",
        icon: "handshake"
      },
      {
        id: 5,
        name: "Server Certificate",
        from: "cloud",
        to: "device",
        duration: "< 1 sec",
        description: "Cloud presents server certificate, device validates chain",
        credentials: ["Server Certificate", "Root CA"],
        secure: true,
        attackSurface: "Rogue server",
        icon: "certificate"
      },
      {
        id: 6,
        name: "Client Certificate Validation",
        from: "cloud",
        to: "device",
        duration: "1-5 sec",
        description: "Cloud validates device certificate against manufacturer CA and CRL/OCSP",
        credentials: ["Device Certificate", "Manufacturer CA", "CRL/OCSP"],
        secure: true,
        attackSurface: "Revocation check bypass",
        icon: "verify"
      },
      {
        id: 7,
        name: "mTLS Established",
        from: "device",
        to: "cloud",
        duration: "< 1 sec",
        description: "Mutual TLS session established with perfect forward secrecy",
        credentials: ["Session Keys", "ECDHE Parameters"],
        secure: true,
        attackSurface: "Side-channel attacks",
        icon: "lock"
      },
      {
        id: 8,
        name: "Device Registration",
        from: "device",
        to: "cloud",
        duration: "2-5 sec",
        description: "Device registers identity and receives operational configuration",
        credentials: ["Device Token", "Config"],
        secure: true,
        attackSurface: "Authorization bypass",
        icon: "register"
      }
    ]
  },
  {
    id: "psk",
    name: "Token-based (Pre-shared Keys)",
    shortName: "PSK",
    description: "Simple provisioning using pre-shared symmetric keys",
    complexity: 1,
    security: 2,
    friction: 2,
    scalability: 2,
    actors: ["Device", "Admin Portal", "Cloud Platform"],
    useCases: ["Prototypes", "Small deployments", "Cost-sensitive"],
    standards: ["TLS-PSK", "DTLS-PSK", "CoAP"],
    steps: [
      {
        id: 1,
        name: "Key Generation",
        from: "cloud",
        to: "portal",
        duration: "Admin action",
        description: "Administrator generates device ID and pre-shared key in cloud portal",
        credentials: ["Device ID", "Pre-shared Key"],
        secure: true,
        attackSurface: "Admin account compromise",
        icon: "key"
      },
      {
        id: 2,
        name: "Key Programming",
        from: "portal",
        to: "device",
        duration: "Manual",
        description: "Key is programmed into device (serial, USB, or hardcoded)",
        credentials: ["Device ID", "Pre-shared Key"],
        secure: false,
        attackSurface: "Key exposure during transfer",
        icon: "program"
      },
      {
        id: 3,
        name: "Device Powers On",
        from: "device",
        to: "device",
        duration: "< 1 sec",
        description: "Device boots with pre-configured credentials",
        credentials: ["Device ID", "Pre-shared Key"],
        secure: true,
        attackSurface: "Key extraction from device",
        icon: "power"
      },
      {
        id: 4,
        name: "Connect to Cloud",
        from: "device",
        to: "cloud",
        duration: "1-5 sec",
        description: "Device connects to cloud endpoint",
        credentials: [],
        secure: false,
        attackSurface: "Endpoint spoofing",
        icon: "connect"
      },
      {
        id: 5,
        name: "PSK Authentication",
        from: "device",
        to: "cloud",
        duration: "1-3 sec",
        description: "TLS-PSK or DTLS-PSK handshake using pre-shared key",
        credentials: ["Device ID", "Pre-shared Key"],
        secure: true,
        attackSurface: "Offline brute force",
        icon: "lock"
      },
      {
        id: 6,
        name: "Session Established",
        from: "cloud",
        to: "device",
        duration: "< 1 sec",
        description: "Encrypted session established for data exchange",
        credentials: ["Session Keys"],
        secure: true,
        attackSurface: "Key reuse across sessions",
        icon: "check"
      }
    ]
  },
  {
    id: "manufacturer",
    name: "Manufacturer Certificate",
    shortName: "Mfr Cert",
    description: "Vendor-managed certificate lifecycle with cloud integration",
    complexity: 4,
    security: 5,
    friction: 1,
    scalability: 5,
    actors: ["Manufacturer", "Device", "Cloud Platform", "Certificate Authority"],
    useCases: ["Branded ecosystems", "Vendor-controlled deployments"],
    standards: ["AWS IoT", "Azure IoT Hub", "Google Cloud IoT"],
    steps: [
      {
        id: 1,
        name: "CA Registration",
        from: "manufacturer",
        to: "cloud",
        duration: "One-time",
        description: "Manufacturer registers CA certificate with cloud platform",
        credentials: ["Manufacturer CA Certificate", "Verification Certificate"],
        secure: true,
        attackSurface: "CA private key compromise",
        icon: "register"
      },
      {
        id: 2,
        name: "Device Manufacturing",
        from: "manufacturer",
        to: "device",
        duration: "Factory",
        description: "Each device receives unique certificate signed by manufacturer CA",
        credentials: ["Device Certificate", "Private Key"],
        secure: true,
        attackSurface: "Manufacturing security",
        icon: "factory"
      },
      {
        id: 3,
        name: "Device Provisioning Record",
        from: "manufacturer",
        to: "cloud",
        duration: "Batch upload",
        description: "Manufacturer uploads device serial/cert mapping to cloud",
        credentials: ["Device Inventory", "Certificate Fingerprints"],
        secure: true,
        attackSurface: "Data tampering",
        icon: "upload"
      },
      {
        id: 4,
        name: "Device First Boot",
        from: "device",
        to: "device",
        duration: "< 1 sec",
        description: "Device powers on with manufacturer certificate",
        credentials: ["Device Certificate"],
        secure: true,
        attackSurface: "Physical tampering",
        icon: "power"
      },
      {
        id: 5,
        name: "TLS Connection",
        from: "device",
        to: "cloud",
        duration: "2-5 sec",
        description: "Device connects with mutual TLS using manufacturer certificate",
        credentials: ["Device Certificate", "Cloud Certificate"],
        secure: true,
        attackSurface: "Certificate validation",
        icon: "lock"
      },
      {
        id: 6,
        name: "Certificate Chain Validation",
        from: "cloud",
        to: "device",
        duration: "1-3 sec",
        description: "Cloud validates certificate chain against registered manufacturer CA",
        credentials: ["Certificate Chain", "CRL"],
        secure: true,
        attackSurface: "Revocation bypass",
        icon: "verify"
      },
      {
        id: 7,
        name: "Auto-Registration",
        from: "cloud",
        to: "cloud",
        duration: "1-5 sec",
        description: "Cloud auto-creates device identity and applies policies",
        credentials: ["Device Identity", "IoT Policies"],
        secure: true,
        attackSurface: "Policy misconfiguration",
        icon: "register"
      },
      {
        id: 8,
        name: "Operational",
        from: "cloud",
        to: "device",
        duration: "< 1 sec",
        description: "Device fully operational with cloud platform",
        credentials: ["Session Credentials"],
        secure: true,
        attackSurface: "None",
        icon: "check"
      }
    ]
  },
  {
    id: "jitp",
    name: "Just-in-Time Provisioning (JITP)",
    shortName: "JITP",
    description: "On-demand provisioning when device first connects",
    complexity: 3,
    security: 4,
    friction: 1,
    scalability: 4,
    actors: ["Device", "Cloud Platform", "Provisioning Template", "Policy Engine"],
    useCases: ["Dynamic fleets", "Multi-tenant platforms", "Flexible deployments"],
    standards: ["AWS IoT JITP", "Azure DPS", "Cloud-native IoT"],
    steps: [
      {
        id: 1,
        name: "Template Configuration",
        from: "admin",
        to: "cloud",
        duration: "One-time",
        description: "Administrator configures provisioning template with device policies",
        credentials: ["Template ID", "Policy Templates"],
        secure: true,
        attackSurface: "Template injection",
        icon: "template"
      },
      {
        id: 2,
        name: "CA Registration",
        from: "admin",
        to: "cloud",
        duration: "One-time",
        description: "Register trusted CA for device certificate validation",
        credentials: ["CA Certificate"],
        secure: true,
        attackSurface: "Unauthorized CA registration",
        icon: "certificate"
      },
      {
        id: 3,
        name: "Device First Connection",
        from: "device",
        to: "cloud",
        duration: "2-5 sec",
        description: "Device connects for first time with bootstrap certificate",
        credentials: ["Device Certificate"],
        secure: true,
        attackSurface: "Certificate theft",
        icon: "connect"
      },
      {
        id: 4,
        name: "Certificate Validation",
        from: "cloud",
        to: "device",
        duration: "1-3 sec",
        description: "Cloud validates certificate against registered CA",
        credentials: ["Certificate Chain"],
        secure: true,
        attackSurface: "Validation bypass",
        icon: "verify"
      },
      {
        id: 5,
        name: "JITP Trigger",
        from: "cloud",
        to: "cloud",
        duration: "< 1 sec",
        description: "Unknown device triggers just-in-time provisioning flow",
        credentials: ["Certificate CN/Subject"],
        secure: true,
        attackSurface: "Resource exhaustion",
        icon: "trigger"
      },
      {
        id: 6,
        name: "Template Execution",
        from: "cloud",
        to: "cloud",
        duration: "1-5 sec",
        description: "Provisioning template creates Thing, certificates, and policies",
        credentials: ["Thing Name", "Policy ARN"],
        secure: true,
        attackSurface: "Privilege escalation",
        icon: "execute"
      },
      {
        id: 7,
        name: "Registration Complete",
        from: "cloud",
        to: "device",
        duration: "< 1 sec",
        description: "Device registered and authorized for operation",
        credentials: ["Device Token", "MQTT Topics"],
        secure: true,
        attackSurface: "Overprivileged access",
        icon: "register"
      },
      {
        id: 8,
        name: "Operational",
        from: "device",
        to: "cloud",
        duration: "Immediate",
        description: "Device begins normal operation with assigned identity",
        credentials: ["Session Credentials"],
        secure: true,
        attackSurface: "None",
        icon: "check"
      }
    ]
  }
]

// State Management
viewModel = ({
  selectedMethod: provisioningMethods[0],
  comparisonMethod: null,
  currentStep: 0,
  isPlaying: false,
  animationSpeed: 1,
  showDetails: true,
  compareMode: false
})

// Selected method state
selectedMethodId = Inputs.select(
  provisioningMethods.map(m => m.id),
  {
    label: "Provisioning Method",
    format: id => provisioningMethods.find(m => m.id === id).name,
    value: "ztp"
  }
)

selectedMethod = provisioningMethods.find(m => m.id === selectedMethodId)

// Comparison method selector
comparisonMethodId = Inputs.select(
  ["none", ...provisioningMethods.filter(m => m.id !== selectedMethodId).map(m => m.id)],
  {
    label: "Compare With",
    format: id => id === "none" ? "None (Single View)" : provisioningMethods.find(m => m.id === id)?.name || "None",
    value: "none"
  }
)

comparisonMethod = comparisonMethodId === "none" ? null : provisioningMethods.find(m => m.id === comparisonMethodId)

// Animation controls
animationSpeed = Inputs.range([0.5, 3], {
  label: "Animation Speed",
  value: 1,
  step: 0.25
})

showTechnicalDetails = Inputs.toggle({
  label: "Show Technical Details",
  value: true
})

// Current step state
currentStepInput = Inputs.range([1, selectedMethod.steps.length], {
  label: "Current Step",
  value: 1,
  step: 1
})

currentStep = currentStepInput - 1

// Animation player
isPlaying = Mutable(false)

playPauseButton = {
  const button = html`<button type="button" aria-label="${isPlaying.value ? 'Pause animation' : 'Play animation'}" style="
    padding: 10px 24px;
    font-size: 14px;
    font-weight: 600;
    background: linear-gradient(135deg, ${ieeeColors.teal}, ${ieeeColors.navy});
    color: white;
    border: none;
    border-radius: 6px;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 8px;
    transition: all 0.2s ease;
  ">
    <span style="font-size: 16px;">${isPlaying.value ? "⏸" : "β–Ά"}</span>
    ${isPlaying.value ? "Pause" : "Play Animation"}
  </button>`;

  button.onclick = () => {
    isPlaying.value = !isPlaying.value;
  };

  return button;
}

// Step-through buttons
stepButtons = html`
<div style="display: flex; gap: 10px; align-items: center;">
  <button type="button" aria-label="Previous step" onclick=${() => { if (currentStepInput > 1) currentStepInput--; }} style="
    padding: 8px 16px;
    background: ${ieeeColors.lightGray};
    border: 1px solid ${ieeeColors.gray};
    border-radius: 4px;
    cursor: pointer;
    font-weight: 500;
  ">← Previous</button>
  <span style="font-weight: 600; color: ${ieeeColors.navy};">
    Step ${currentStep + 1} of ${selectedMethod.steps.length}
  </span>
  <button type="button" aria-label="Next step" onclick=${() => { if (currentStepInput < selectedMethod.steps.length) currentStepInput++; }} style="
    padding: 8px 16px;
    background: ${ieeeColors.lightGray};
    border: 1px solid ${ieeeColors.gray};
    border-radius: 4px;
    cursor: pointer;
    font-weight: 500;
  ">Next β†’</button>
</div>
`

// Main visualization
mainVisualization = {
  const width = 900;
  const height = comparisonMethod ? 700 : 550;

  const svg = d3.create("svg")
    .attr("viewBox", [0, 0, width, height])
    .attr("width", "100%")
    .attr("height", height)
    .style("font-family", "system-ui, -apple-system, sans-serif")
    .style("background", "white");

  // Define arrow markers
  const defs = svg.append("defs");

  defs.append("marker")
    .attr("id", "arrowhead-secure")
    .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", ieeeColors.teal);

  defs.append("marker")
    .attr("id", "arrowhead-insecure")
    .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", ieeeColors.orange);

  // Gradient for secure channel
  const gradient = defs.append("linearGradient")
    .attr("id", "secure-gradient")
    .attr("x1", "0%")
    .attr("y1", "0%")
    .attr("x2", "100%")
    .attr("y2", "0%");

  gradient.append("stop")
    .attr("offset", "0%")
    .attr("stop-color", ieeeColors.teal);

  gradient.append("stop")
    .attr("offset", "100%")
    .attr("stop-color", ieeeColors.navy);

  // Draw single or comparison view
  if (comparisonMethod) {
    drawComparisonView(svg, width, height, selectedMethod, comparisonMethod, currentStep);
  } else {
    drawSingleView(svg, width, height, selectedMethod, currentStep);
  }

  return svg.node();

  function drawSingleView(svg, width, height, method, currentStep) {
    // Title
    svg.append("text")
      .attr("x", width / 2)
      .attr("y", 30)
      .attr("text-anchor", "middle")
      .attr("font-size", "18px")
      .attr("font-weight", "700")
      .attr("fill", ieeeColors.navy)
      .text(method.name);

    // Swimlane setup
    const actors = method.actors;
    const laneWidth = (width - 100) / actors.length;
    const laneStartY = 60;
    const laneHeight = height - 100;

    // Draw swimlanes
    actors.forEach((actor, i) => {
      const x = 50 + i * laneWidth;

      // Lane background
      svg.append("rect")
        .attr("x", x)
        .attr("y", laneStartY)
        .attr("width", laneWidth)
        .attr("height", laneHeight)
        .attr("fill", i % 2 === 0 ? ieeeColors.lightGray : "white")
        .attr("stroke", ieeeColors.gray)
        .attr("stroke-width", 0.5);

      // Actor header
      svg.append("rect")
        .attr("x", x)
        .attr("y", laneStartY)
        .attr("width", laneWidth)
        .attr("height", 35)
        .attr("fill", ieeeColors.navy);

      svg.append("text")
        .attr("x", x + laneWidth / 2)
        .attr("y", laneStartY + 22)
        .attr("text-anchor", "middle")
        .attr("font-size", "12px")
        .attr("font-weight", "600")
        .attr("fill", "white")
        .text(actor);
    });

    // Draw steps
    const stepHeight = (laneHeight - 60) / method.steps.length;

    method.steps.forEach((step, i) => {
      const y = laneStartY + 50 + i * stepHeight;
      const isActive = i === currentStep;
      const isPast = i < currentStep;
      const isFuture = i > currentStep;

      // Step number circle
      const stepX = 25;
      svg.append("circle")
        .attr("cx", stepX)
        .attr("cy", y + 15)
        .attr("r", 12)
        .attr("fill", isActive ? ieeeColors.teal : isPast ? ieeeColors.navy : ieeeColors.lightGray)
        .attr("stroke", isActive ? ieeeColors.navy : "none")
        .attr("stroke-width", 2);

      svg.append("text")
        .attr("x", stepX)
        .attr("y", y + 19)
        .attr("text-anchor", "middle")
        .attr("font-size", "10px")
        .attr("font-weight", "600")
        .attr("fill", isFuture ? ieeeColors.gray : "white")
        .text(step.id);

      // Get actor positions
      const fromActor = getActorIndex(step.from, actors);
      const toActor = getActorIndex(step.to, actors);

      const fromX = 50 + fromActor * laneWidth + laneWidth / 2;
      const toX = 50 + toActor * laneWidth + laneWidth / 2;

      // Draw message flow
      if (fromActor !== toActor) {
        const arrowOffset = fromX < toX ? -15 : 15;

        svg.append("line")
          .attr("x1", fromX + (fromX < toX ? 20 : -20))
          .attr("y1", y + 15)
          .attr("x2", toX + arrowOffset)
          .attr("y2", y + 15)
          .attr("stroke", step.secure ? ieeeColors.teal : ieeeColors.orange)
          .attr("stroke-width", isActive ? 3 : isPast ? 2 : 1)
          .attr("stroke-dasharray", step.secure ? "none" : "5,3")
          .attr("marker-end", `url(#arrowhead-${step.secure ? "secure" : "insecure"})`)
          .attr("opacity", isFuture ? 0.3 : 1);

        // Channel indicator
        if (isActive && showTechnicalDetails) {
          svg.append("text")
            .attr("x", (fromX + toX) / 2)
            .attr("y", y + 5)
            .attr("text-anchor", "middle")
            .attr("font-size", "8px")
            .attr("fill", step.secure ? ieeeColors.teal : ieeeColors.orange)
            .text(step.secure ? "Secure" : "Insecure");
        }
      } else {
        // Self-referencing action
        svg.append("ellipse")
          .attr("cx", fromX)
          .attr("cy", y + 15)
          .attr("rx", 25)
          .attr("ry", 12)
          .attr("fill", "none")
          .attr("stroke", isActive ? ieeeColors.teal : isPast ? ieeeColors.navy : ieeeColors.gray)
          .attr("stroke-width", isActive ? 2 : 1)
          .attr("opacity", isFuture ? 0.3 : 1);
      }

      // Step icon/symbol based on type
      const iconX = fromActor === toActor ? fromX : (fromX + toX) / 2;
      const icons = {
        factory: "F",
        power: "P",
        search: "S",
        lock: "L",
        verify: "V",
        download: "D",
        cloud: "C",
        check: "OK",
        qrcode: "QR",
        wifi: "W",
        key: "K",
        network: "N",
        certificate: "Ct",
        handshake: "H",
        register: "R",
        program: "Pg",
        connect: "Cn",
        upload: "U",
        template: "T",
        trigger: "Tr",
        execute: "E"
      };

      if (isActive && !isFuture) {
        svg.append("text")
          .attr("x", iconX)
          .attr("y", y + 35)
          .attr("text-anchor", "middle")
          .attr("font-size", "10px")
          .attr("font-weight", "bold")
          .attr("fill", ieeeColors.navy)
          .text(icons[step.icon] || "●");
      }
    });

    // Current step details panel
    const detailY = laneStartY + laneHeight - 80;
    const currentStepData = method.steps[currentStep];

    // Details background
    svg.append("rect")
      .attr("x", 50)
      .attr("y", detailY)
      .attr("width", width - 100)
      .attr("height", 75)
      .attr("rx", 8)
      .attr("fill", "white")
      .attr("stroke", ieeeColors.teal)
      .attr("stroke-width", 2);

    // Step name
    svg.append("text")
      .attr("x", 65)
      .attr("y", detailY + 20)
      .attr("font-size", "14px")
      .attr("font-weight", "700")
      .attr("fill", ieeeColors.navy)
      .text(`Step ${currentStep + 1}: ${currentStepData.name}`);

    // Duration
    svg.append("text")
      .attr("x", width - 65)
      .attr("y", detailY + 20)
      .attr("text-anchor", "end")
      .attr("font-size", "12px")
      .attr("fill", ieeeColors.gray)
      .text(`Duration: ${currentStepData.duration}`);

    // Description
    svg.append("text")
      .attr("x", 65)
      .attr("y", detailY + 40)
      .attr("font-size", "11px")
      .attr("fill", ieeeColors.darkGray)
      .text(currentStepData.description);

    // Credentials
    if (showTechnicalDetails && currentStepData.credentials.length > 0) {
      svg.append("text")
        .attr("x", 65)
        .attr("y", detailY + 58)
        .attr("font-size", "10px")
        .attr("fill", ieeeColors.teal)
        .text(`Credentials: ${currentStepData.credentials.join(", ")}`);
    }

    // Security indicator
    svg.append("rect")
      .attr("x", 65)
      .attr("y", detailY + 62)
      .attr("width", 8)
      .attr("height", 8)
      .attr("rx", 2)
      .attr("fill", currentStepData.secure ? ieeeColors.green : ieeeColors.orange);

    svg.append("text")
      .attr("x", 78)
      .attr("y", detailY + 70)
      .attr("font-size", "9px")
      .attr("fill", ieeeColors.gray)
      .text(currentStepData.secure ? "Secure Channel" : "Insecure Channel");
  }

  function drawComparisonView(svg, width, height, method1, method2, currentStep) {
    const halfWidth = width / 2 - 20;

    // Method 1 (left)
    const g1 = svg.append("g").attr("transform", "translate(0, 0)");
    drawCompactView(g1, halfWidth, height, method1, currentStep, 10);

    // Divider
    svg.append("line")
      .attr("x1", width / 2)
      .attr("y1", 20)
      .attr("x2", width / 2)
      .attr("y2", height - 20)
      .attr("stroke", ieeeColors.gray)
      .attr("stroke-width", 2)
      .attr("stroke-dasharray", "5,5");

    // Method 2 (right)
    const g2 = svg.append("g").attr("transform", `translate(${width / 2 + 10}, 0)`);
    drawCompactView(g2, halfWidth, height, method2, Math.min(currentStep, method2.steps.length - 1), 0);
  }

  function drawCompactView(g, width, height, method, currentStep, offsetX) {
    // Title
    g.append("text")
      .attr("x", width / 2 + offsetX)
      .attr("y", 25)
      .attr("text-anchor", "middle")
      .attr("font-size", "14px")
      .attr("font-weight", "700")
      .attr("fill", ieeeColors.navy)
      .text(method.shortName);

    // Steps list
    const stepHeight = Math.min(50, (height - 100) / method.steps.length);
    const startY = 50;

    method.steps.forEach((step, i) => {
      const y = startY + i * stepHeight;
      const isActive = i === currentStep;
      const isPast = i < currentStep;

      // Step indicator
      g.append("circle")
        .attr("cx", 25 + offsetX)
        .attr("cy", y + 12)
        .attr("r", 8)
        .attr("fill", isActive ? ieeeColors.teal : isPast ? ieeeColors.navy : ieeeColors.lightGray);

      g.append("text")
        .attr("x", 25 + offsetX)
        .attr("y", y + 16)
        .attr("text-anchor", "middle")
        .attr("font-size", "9px")
        .attr("font-weight", "600")
        .attr("fill", "white")
        .text(step.id);

      // Step name
      g.append("text")
        .attr("x", 45 + offsetX)
        .attr("y", y + 16)
        .attr("font-size", "10px")
        .attr("font-weight", isActive ? "600" : "400")
        .attr("fill", isActive ? ieeeColors.navy : ieeeColors.darkGray)
        .text(step.name.substring(0, 25) + (step.name.length > 25 ? "..." : ""));

      // Security indicator
      g.append("circle")
        .attr("cx", width - 15 + offsetX)
        .attr("cy", y + 12)
        .attr("r", 5)
        .attr("fill", step.secure ? ieeeColors.green : ieeeColors.orange);

      // Duration
      if (isActive) {
        g.append("text")
          .attr("x", width - 30 + offsetX)
          .attr("y", y + 16)
          .attr("text-anchor", "end")
          .attr("font-size", "8px")
          .attr("fill", ieeeColors.gray)
          .text(step.duration);
      }
    });

    // Current step detail
    const detailY = height - 120;
    const step = method.steps[currentStep];

    g.append("rect")
      .attr("x", 10 + offsetX)
      .attr("y", detailY)
      .attr("width", width - 20)
      .attr("height", 100)
      .attr("rx", 6)
      .attr("fill", ieeeColors.lightGray)
      .attr("stroke", ieeeColors.teal)
      .attr("stroke-width", 1);

    g.append("text")
      .attr("x", 20 + offsetX)
      .attr("y", detailY + 18)
      .attr("font-size", "11px")
      .attr("font-weight", "600")
      .attr("fill", ieeeColors.navy)
      .text(step.name);

    // Wrap description
    const words = step.description.split(" ");
    let line = "";
    let lineNum = 0;
    const maxWidth = 35;

    words.forEach(word => {
      if ((line + " " + word).length > maxWidth) {
        g.append("text")
          .attr("x", 20 + offsetX)
          .attr("y", detailY + 35 + lineNum * 12)
          .attr("font-size", "9px")
          .attr("fill", ieeeColors.darkGray)
          .text(line);
        line = word;
        lineNum++;
      } else {
        line = line ? line + " " + word : word;
      }
    });
    if (line) {
      g.append("text")
        .attr("x", 20 + offsetX)
        .attr("y", detailY + 35 + lineNum * 12)
        .attr("font-size", "9px")
        .attr("fill", ieeeColors.darkGray)
        .text(line);
    }

    // Attack surface
    g.append("text")
      .attr("x", 20 + offsetX)
      .attr("y", detailY + 88)
      .attr("font-size", "8px")
      .attr("fill", ieeeColors.orange)
      .text(`Attack: ${step.attackSurface}`);
  }

  function getActorIndex(actorName, actors) {
    const mapping = {
      "device": 0,
      "manufacturer": 0,
      "network": 1,
      "server": 2,
      "cloud": actors.length - 1,
      "user": 2,
      "app": 1,
      "portal": 1,
      "admin": 0
    };

    const normalizedName = actorName.toLowerCase();
    if (mapping.hasOwnProperty(normalizedName)) {
      return Math.min(mapping[normalizedName], actors.length - 1);
    }
    return 0;
  }
}

// Flow Timeline View
flowTimeline = {
  const method = selectedMethod;
  const totalTime = method.steps.reduce((acc, step) => {
    const time = parseTime(step.duration);
    return acc + time;
  }, 0);

  function parseTime(duration) {
    if (duration.includes("sec")) {
      const match = duration.match(/(\d+)/);
      return match ? parseInt(match[1]) : 3;
    }
    if (duration.includes("Offline") || duration.includes("Factory") || duration.includes("Manual")) {
      return 0;
    }
    return 2;
  }

  return html`
    <div style="
      background: white;
      border: 1px solid ${ieeeColors.gray};
      border-radius: 12px;
      padding: 20px;
      margin-top: 20px;
    ">
      <h3 style="margin: 0 0 15px 0; color: ${ieeeColors.navy}; font-size: 16px;">
        Provisioning Timeline
      </h3>

      <div style="position: relative; padding: 20px 0;">
        <!-- Timeline bar -->
        <div style="
          position: absolute;
          top: 50%;
          left: 40px;
          right: 40px;
          height: 4px;
          background: ${ieeeColors.lightGray};
          transform: translateY(-50%);
          border-radius: 2px;
        "></div>

        <!-- Progress bar -->
        <div style="
          position: absolute;
          top: 50%;
          left: 40px;
          width: ${((currentStep + 1) / method.steps.length) * (100 - 8)}%;
          height: 4px;
          background: linear-gradient(90deg, ${ieeeColors.teal}, ${ieeeColors.navy});
          transform: translateY(-50%);
          border-radius: 2px;
          transition: width 0.3s ease;
        "></div>

        <!-- Step markers -->
        <div style="display: flex; justify-content: space-between; padding: 0 20px;">
          ${method.steps.map((step, i) => html`
            <div style="
              display: flex;
              flex-direction: column;
              align-items: center;
              z-index: 1;
            ">
              <div style="
                width: ${i === currentStep ? 24 : 16}px;
                height: ${i === currentStep ? 24 : 16}px;
                border-radius: 50%;
                background: ${i <= currentStep ? ieeeColors.teal : ieeeColors.lightGray};
                border: 3px solid ${i === currentStep ? ieeeColors.navy : "transparent"};
                display: flex;
                align-items: center;
                justify-content: center;
                font-size: ${i === currentStep ? 10 : 8}px;
                font-weight: 600;
                color: ${i <= currentStep ? "white" : ieeeColors.gray};
                transition: all 0.3s ease;
                box-shadow: ${i === currentStep ? `0 0 10px ${ieeeColors.teal}` : "none"};
              ">
                ${i + 1}
              </div>
              <div style="
                margin-top: 8px;
                font-size: 9px;
                color: ${i === currentStep ? ieeeColors.navy : ieeeColors.gray};
                font-weight: ${i === currentStep ? 600 : 400};
                text-align: center;
                max-width: 80px;
                overflow: hidden;
                text-overflow: ellipsis;
                white-space: nowrap;
              ">
                ${step.name.split(" ")[0]}
              </div>
              <div style="
                font-size: 8px;
                color: ${ieeeColors.gray};
                margin-top: 2px;
              ">
                ${step.duration}
              </div>
            </div>
          `)}
        </div>
      </div>

      <!-- Estimated total time -->
      <div style="
        text-align: center;
        margin-top: 10px;
        font-size: 12px;
        color: ${ieeeColors.darkGray};
      ">
        Estimated Total Time: <strong style="color: ${ieeeColors.navy};">
          ${totalTime > 60 ? `${Math.round(totalTime / 60)} min` : `${totalTime} sec`}
        </strong> (online steps only)
      </div>
    </div>
  `;
}
```
ImportantOJS Syntax Error (line 803, column 96)Assignment to constant variable currentStepInput

1475.3.1 Controls

1475.3.2 Provisioning Flow Visualization

1475.3.3 Timeline View

1475.4 Understanding the Visualizer

TipHow to Use This Tool
  1. Select a Provisioning Method - Choose from six common IoT provisioning approaches
  2. Compare Methods - Optionally select a second method for side-by-side comparison
  3. Step Through the Flow - Use controls to animate through provisioning steps
  4. Analyze Security - Review attack surfaces, credentials, and security status for each step

1475.5 Summary

This visualizer demonstrates the step-by-step flow of different device provisioning methods:

  • Zero-Touch Provisioning (ZTP) - Fully automated for enterprise deployments
  • QR Code / App-based - User-friendly for consumer IoT
  • Certificate-based (X.509) - High security with PKI infrastructure
  • Token-based (PSK) - Simple but limited scalability
  • Manufacturer Certificate - Vendor-managed lifecycle
  • Just-in-Time Provisioning (JITP) - Dynamic cloud-native approach

1475.6 What’s Next

Continue exploring device provisioning security: