From 72b0c07adc092c7e3f8d1a066e70bbd3f6d07309 Mon Sep 17 00:00:00 2001 From: Aleksei Fedorov Date: Sat, 14 Mar 2026 19:03:04 +0000 Subject: [PATCH] crypto subsystem --- index.html | 2 +- static/js/crypto-belt.js | 256 ++++++++++++++++++++++++-- templates/linux-crypto-subsystem.html | 2 +- 3 files changed, 243 insertions(+), 17 deletions(-) diff --git a/index.html b/index.html index 20a079c..3404d03 100755 --- a/index.html +++ b/index.html @@ -100,7 +100,7 @@

Linux Kernel Ring 0 Visualization

- + diff --git a/static/js/crypto-belt.js b/static/js/crypto-belt.js index dd6ec2d..5d3deca 100644 --- a/static/js/crypto-belt.js +++ b/static/js/crypto-belt.js @@ -23,6 +23,7 @@ class CryptoSubsystemVisualization { this.algorithmModes = ['AES', 'SHA', 'CHACHA20']; this.selectedClientFilters = new Set(); this.selectedRequesterFilter = null; + this.selectedImplementationClassFilter = null; } init(containerId = 'crypto-belt-container') { @@ -616,6 +617,7 @@ class CryptoSubsystemVisualization { .style('cursor', 'pointer') .on('click', () => { this.selectedCompetitionAlgorithm = mode; + this.selectedImplementationClassFilter = null; this.renderFlowMap(this.lastPayload || this.normalizeTelemetry(this.getFallbackTelemetry())); }); @@ -849,6 +851,224 @@ class CryptoSubsystemVisualization { .text(`reason: ${String(pipeline.fallback_reason || 'not-triggered')}`); } + getImplementationClass(implName) { + const name = String(implName || '').toLowerCase(); + if (!name) return 'generic'; + if (name.includes('aesni') || name.includes('vaes') || name.includes('ce')) return 'cpu-instr'; + if (name.includes('avx') || name.includes('sse') || name.includes('simd') || name.includes('neon')) return 'simd'; + if (name.includes('qat') || name.includes('virtio')) return 'offload'; + if (name.includes('generic')) return 'generic'; + return 'generic'; + } + + hashText(text) { + let hash = 0; + const src = String(text || ''); + for (let i = 0; i < src.length; i += 1) { + hash = ((hash << 5) - hash) + src.charCodeAt(i); + hash |= 0; + } + return Math.abs(hash); + } + + laneMatchesSelectedAlgorithm(lane) { + const selectedAlgo = String(this.selectedCompetitionAlgorithm || 'AES').toLowerCase(); + const protocol = String(lane?.protocol || '').toUpperCase(); + const algo = String(lane?.algorithm || '').toLowerCase(); + if (selectedAlgo === 'aes') { + return algo.includes('aes') || protocol === 'TLS'; + } + if (selectedAlgo === 'sha') { + return algo.includes('sha') || protocol === 'TLS'; + } + if (selectedAlgo === 'chacha20') { + return algo.includes('chacha') || protocol === 'WIREGUARD' || protocol === 'SSH'; + } + return true; + } + + laneMatchesSelectedImplementationClass(lane) { + const cls = String(this.selectedImplementationClassFilter || ''); + if (!cls) return true; + if (!this.laneMatchesSelectedAlgorithm(lane)) return false; + + const protocol = String(lane?.protocol || '').toUpperCase(); + const sourceKind = String(lane?.source_kind || '').toLowerCase(); + + if (cls === 'cpu-instr') { + // CPU instruction path is mainly relevant for AES/SHA-family flows. + const selectedAlgo = String(this.selectedCompetitionAlgorithm || 'AES').toLowerCase(); + return selectedAlgo === 'aes' || selectedAlgo === 'sha'; + } + if (cls === 'simd') { + return true; + } + if (cls === 'offload') { + return protocol === 'TLS' || protocol === 'WIREGUARD' || sourceKind === 'connection'; + } + if (cls === 'generic') { + return true; + } + return true; + } + + drawAlgorithmMaterialCard(layer, meta, width, height) { + const comp = this.getCompetitionPayload(meta); + const pipeline = this.getDecisionPipelinePayload(meta); + const request = String(comp.request || this.selectedCompetitionAlgorithm || 'AES').toUpperCase(); + const impls = Array.isArray(comp.implementations) ? comp.implementations.slice(0, 8) : []; + const selectedName = String(comp?.selected?.name || '').toLowerCase(); + const selectedPriority = Number(comp?.selected?.priority || 0); + const requesters = Array.isArray(pipeline?.requesters) ? pipeline.requesters : []; + const topRequester = requesters.length ? requesters[0] : null; + + const cardW = Math.max(320, Math.min(430, Math.floor(width * 0.27))); + const cardH = 214; + const cardX = Math.floor(width * 0.43); + // Align card vertically with the Stage-1 left stack (starts at KERNEL CRYPTO CLIENTS). + const stageGap = 12; + const stageClientsH = 134; + const stageQueueH = 86; + const stageOffloadH = 116; + const stageTotalH = stageClientsH + stageQueueH + stageOffloadH + (stageGap * 2); + const stageBaseY = Math.max(180, height - stageTotalH - 20); + const cardY = stageBaseY; + + const card = layer.append('g').attr('class', 'crypto-material-card'); + card.append('rect') + .attr('x', cardX) + .attr('y', cardY) + .attr('width', cardW) + .attr('height', cardH) + .attr('rx', 9) + .style('fill', 'rgba(7, 10, 15, 0.9)') + .style('stroke', 'rgba(162, 176, 198, 0.32)') + .style('stroke-width', 1); + + card.append('text') + .attr('x', cardX + 14) + .attr('y', cardY + 22) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '11px') + .style('letter-spacing', '0.5px') + .style('fill', '#d7ddea') + .text('ALGORITHM MATERIAL CARD'); + + const leftX = cardX + 14; + const baseY = cardY + 44; + const details = [ + `${request} - UID: ${String(comp?.selected?.type || 'tfm').toUpperCase()}`, + `classification: kernel crypto algorithm`, + `requestor: ${topRequester ? topRequester.name : 'unknown'}`, + `tfm lookup: ${String(pipeline.tfm_lookup || 'crypto_lookup(?)')}`, + `selected: ${String(comp?.selected?.name || 'unknown')}`, + `priority: ${selectedPriority || '-'}`, + `capability: ${String(pipeline.capability_check || 'generic-cpu-only')}` + ]; + details.forEach((line, idx) => { + card.append('text') + .attr('x', leftX) + .attr('y', baseY + idx * 21) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', idx === 0 ? '10px' : '9.5px') + .style('fill', idx === 0 ? '#f0f4fb' : '#a7b3c4') + .text(line); + }); + + const cloudX = cardX + Math.floor(cardW * 0.57); + const cloudY = cardY + 34; + const cloudW = cardW - Math.floor(cardW * 0.57) - 14; + const cloudH = cardH - 46; + card.append('rect') + .attr('x', cloudX) + .attr('y', cloudY) + .attr('width', cloudW) + .attr('height', cloudH) + .attr('rx', 7) + .style('fill', 'rgba(12, 16, 22, 0.62)') + .style('stroke', 'rgba(112, 123, 140, 0.28)') + .style('stroke-width', 0.8); + + const maxPriority = Math.max(...impls.map((i) => Number(i.priority || 0)), 1); + const classAccent = { + 'cpu-instr': '#6ed0ff', + simd: '#d9b1f4', + offload: '#95f0cf', + generic: '#9da9bd' + }; + + impls.forEach((impl, idx) => { + const name = String(impl.name || 'unknown'); + const prio = Number(impl.priority || 0); + const cls = this.getImplementationClass(name); + const jitter = this.hashText(name); + const col = idx % 3; + const row = Math.floor(idx / 3); + const localX = 16 + col * Math.max(22, Math.floor((cloudW - 40) / 3)) + (jitter % 11) - 5; + const localY = 20 + row * 40 + ((Math.floor(jitter / 7) % 11) - 5); + const cx = Math.max(cloudX + 12, Math.min(cloudX + cloudW - 12, cloudX + localX)); + const cy = Math.max(cloudY + 12, Math.min(cloudY + cloudH - 12, cloudY + localY)); + const radius = 3.8 + ((prio / maxPriority) * 5.8); + const isSelected = name.toLowerCase() === selectedName; + const isClassFiltered = this.selectedImplementationClassFilter === cls; + + card.append('circle') + .attr('cx', cx) + .attr('cy', cy) + .attr('r', radius) + .style('fill', isSelected ? classAccent[cls] : 'rgba(18, 24, 32, 0.92)') + .style('stroke', isSelected ? classAccent[cls] : classAccent[cls]) + .style('stroke-width', isSelected ? 1.55 : 0.9) + .style('opacity', isSelected ? 0.98 : (isClassFiltered ? 0.95 : 0.82)) + .style('cursor', 'pointer') + .on('click', () => { + this.selectedImplementationClassFilter = this.selectedImplementationClassFilter === cls ? null : cls; + this.renderFlowMap(this.lastPayload || this.normalizeTelemetry(this.getFallbackTelemetry())); + }); + + if (isSelected) { + card.append('circle') + .attr('cx', cx) + .attr('cy', cy) + .attr('r', radius + 3.5) + .style('fill', 'none') + .style('stroke', classAccent[cls]) + .style('stroke-width', 0.85) + .style('opacity', 0.7); + } + + if (isClassFiltered) { + card.append('circle') + .attr('cx', cx) + .attr('cy', cy) + .attr('r', radius + 2.1) + .style('fill', 'none') + .style('stroke', '#e5ebf5') + .style('stroke-width', 0.8) + .style('opacity', 0.88) + .style('pointer-events', 'none'); + } + }); + + card.append('text') + .attr('x', cloudX + 8) + .attr('y', cardY + cardH - 12) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '8.5px') + .style('fill', '#8f9eb3') + .text('dots = implementations, size = priority'); + + if (this.selectedImplementationClassFilter) { + card.append('text') + .attr('x', leftX) + .attr('y', cardY + cardH - 12) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '8.5px') + .style('fill', '#c8d6ea') + .text(`class filter: ${this.selectedImplementationClassFilter} (click dot to clear)`); + } + } + drawStage1Panels(layer, meta, width, height) { const stage = meta?.crypto_stage1 || {}; const clients = Array.isArray(stage.kernel_clients) ? stage.kernel_clients.slice(0, 6) : []; @@ -1065,16 +1285,7 @@ class CryptoSubsystemVisualization { const protocol = String(lane?.protocol || '').toUpperCase(); const algo = String(lane?.algorithm || '').toLowerCase(); - const selectedAlgo = String(this.selectedCompetitionAlgorithm || 'AES').toLowerCase(); - let algoMatch = true; - if (selectedAlgo === 'aes') { - algoMatch = algo.includes('aes') || protocol === 'TLS'; - } else if (selectedAlgo === 'sha') { - algoMatch = algo.includes('sha') || protocol === 'TLS'; - } else if (selectedAlgo === 'chacha20') { - algoMatch = algo.includes('chacha') || protocol === 'WIREGUARD' || protocol === 'SSH'; - } - if (!algoMatch) return false; + if (!this.laneMatchesSelectedAlgorithm(lane)) return false; if (reqKind === 'kernel-client') { return this.laneMatchesClient(reqName, lane); @@ -1187,12 +1398,15 @@ class CryptoSubsystemVisualization { this.drawProtocolLegend(layer); this.drawAlgorithmCompetition(layer, payload?.meta || {}, width); this.drawDecisionPipeline(layer, payload?.meta || {}, width, height); + this.drawAlgorithmMaterialCard(layer, payload?.meta || {}, width, height); this.drawStage1Panels(layer, payload?.meta || {}, width, height); const sourceLanes = Array.isArray(payload.items) ? payload.items : []; - const lanes = sourceLanes.filter( - (lane) => this.laneMatchesSelectedClients(lane) && this.laneMatchesSelectedRequester(lane) - ); + const lanes = sourceLanes.filter((lane) => ( + this.laneMatchesSelectedClients(lane) + && this.laneMatchesSelectedRequester(lane) + && this.laneMatchesSelectedImplementationClass(lane) + )); const topY = 150; const protocolY = 250; const cryptoY = 350; @@ -1211,6 +1425,9 @@ class CryptoSubsystemVisualization { const requesterLabel = this.selectedRequesterFilter ? ` | requester:${this.selectedRequesterFilter.name}` : ''; + const classLabel = this.selectedImplementationClassFilter + ? ` | class:${this.selectedImplementationClassFilter}` + : ''; layer.append('text') .attr('x', width * 0.42) .attr('y', 320) @@ -1218,13 +1435,18 @@ class CryptoSubsystemVisualization { .style('font-family', 'Share Tech Mono, monospace') .style('font-size', '12px') .style('fill', '#8fa0b6') - .text(`NO ACTIVE PATHS FOR ${selectedLabel.toUpperCase()}${requesterLabel.toUpperCase()}`); + .text(`NO ACTIVE PATHS FOR ${selectedLabel.toUpperCase()}${requesterLabel.toUpperCase()}${classLabel.toUpperCase()}`); } lanes.forEach((lane, idx) => { const x = startX + laneStep * idx; const intensity = Math.min(1 + lane.weight * 0.35, 2.2); - const emphasis = Boolean(lane.isNew || lane.isHot || this.selectedRequesterFilter); + const emphasis = Boolean( + lane.isNew + || lane.isHot + || this.selectedRequesterFilter + || this.selectedImplementationClassFilter + ); const laneGroup = layer.append('g').attr('class', 'crypto-lane'); const pNode = this.drawNode(laneGroup, x, topY, lane.process, 'process', intensity, lane.palette, emphasis); @@ -1289,12 +1511,16 @@ class CryptoSubsystemVisualization { .style('fill', '#d2d9e5') .text( this.selectedClientFilters.size || this.selectedRequesterFilter + || this.selectedImplementationClassFilter ? `ACTIVE PATHS (${[ this.selectedClientFilters.size ? Array.from(this.selectedClientFilters).join(' + ') : null, this.selectedRequesterFilter ? `requester:${this.selectedRequesterFilter.name}` + : null, + this.selectedImplementationClassFilter + ? `class:${this.selectedImplementationClassFilter}` : null ].filter(Boolean).join(' | ')})` : 'ACTIVE PATHS' diff --git a/templates/linux-crypto-subsystem.html b/templates/linux-crypto-subsystem.html index 19510c2..d7997ed 100644 --- a/templates/linux-crypto-subsystem.html +++ b/templates/linux-crypto-subsystem.html @@ -135,7 +135,7 @@

Linux Crypto Kernel Visualization

- +