mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-05-03 05:55:18 +00:00
feat: detect and flag inconsistent hash sizes across adverts
Tracks all hash_size values seen per node. If a node has sent adverts
with different hash sizes, flags it as hash_size_inconsistent with a
yellow ⚠️ badge on both side pane and detail page. Tooltip mentions
likely firmware bug (pre-1.14.1). Stats row shows all sizes seen.
This commit is contained in:
+3
-3
@@ -119,7 +119,7 @@
|
||||
body.innerHTML = `
|
||||
<div class="node-full-card" style="padding:12px 16px;margin-bottom:8px">
|
||||
<div class="node-detail-name" style="font-size:20px">${escapeHtml(n.name || '(unnamed)')}</div>
|
||||
<div style="margin:4px 0 6px"><span class="badge" style="background:${roleColor}20;color:${roleColor}">${n.role}</span> ${n.hash_size ? `<span class="badge" style="background:var(--nav-bg);color:var(--nav-text);font-family:var(--mono)">${n.public_key.slice(0, n.hash_size * 2).toUpperCase()}</span>` : ''} ${statusLabel}</div>
|
||||
<div style="margin:4px 0 6px"><span class="badge" style="background:${roleColor}20;color:${roleColor}">${n.role}</span> ${n.hash_size ? `<span class="badge" style="background:var(--nav-bg);color:var(--nav-text);font-family:var(--mono)">${n.public_key.slice(0, n.hash_size * 2).toUpperCase()}</span>` : ''} ${n.hash_size_inconsistent ? '<span class="badge" style="background:var(--status-yellow);color:#000;font-size:10px" title="Adverts show different hash sizes — likely firmware bug (pre-1.14.1)">⚠️ hash mismatch</span>' : ''} ${statusLabel}</div>
|
||||
<div class="node-detail-key mono" style="font-size:11px;word-break:break-all;margin-bottom:6px">${n.public_key}</div>
|
||||
<div>
|
||||
<button class="btn-primary" id="copyUrlBtn" style="font-size:12px;padding:4px 10px">📋 Copy URL</button>
|
||||
@@ -143,7 +143,7 @@
|
||||
${stats.avgSnr != null ? `<tr><td>Avg SNR</td><td>${stats.avgSnr.toFixed(1)} dB</td></tr>` : ''}
|
||||
${stats.avgHops ? `<tr><td>Avg Hops</td><td>${stats.avgHops}</td></tr>` : ''}
|
||||
${hasLoc ? `<tr><td>Location</td><td>${n.lat.toFixed(5)}, ${n.lon.toFixed(5)}</td></tr>` : ''}
|
||||
<tr><td>Hash Prefix</td><td>${n.hash_size ? '<code style="font-family:var(--mono);font-weight:700">' + n.public_key.slice(0, n.hash_size * 2).toUpperCase() + '</code> (' + n.hash_size + '-byte)' : 'Unknown'}</td></tr>
|
||||
<tr><td>Hash Prefix</td><td>${n.hash_size ? '<code style="font-family:var(--mono);font-weight:700">' + n.public_key.slice(0, n.hash_size * 2).toUpperCase() + '</code> (' + n.hash_size + '-byte)' : 'Unknown'}${n.hash_size_inconsistent ? ' <span style="color:var(--status-yellow)" title="Seen hash sizes: ' + (n.hash_sizes_seen || []).join(', ') + '-byte">⚠️ inconsistent — possible firmware bug</span>' : ''}</td></tr>
|
||||
</table>
|
||||
|
||||
${observers.length ? `<div class="node-full-card">
|
||||
@@ -503,7 +503,7 @@
|
||||
panel.innerHTML = `
|
||||
<div class="node-detail">
|
||||
<div class="node-detail-name">${escapeHtml(n.name || '(unnamed)')}</div>
|
||||
<div class="node-detail-role"><span class="badge" style="background:${roleColor}20;color:${roleColor}">${n.role}</span> ${n.hash_size ? `<span class="badge" style="background:var(--nav-bg);color:var(--nav-text);font-family:var(--mono)">${n.public_key.slice(0, n.hash_size * 2).toUpperCase()}</span>` : ''} ${statusLabel}
|
||||
<div class="node-detail-role"><span class="badge" style="background:${roleColor}20;color:${roleColor}">${n.role}</span> ${n.hash_size ? `<span class="badge" style="background:var(--nav-bg);color:var(--nav-text);font-family:var(--mono)">${n.public_key.slice(0, n.hash_size * 2).toUpperCase()}</span>` : ''} ${n.hash_size_inconsistent ? '<span class="badge" style="background:var(--status-yellow);color:#000;font-size:10px" title="Adverts show different hash sizes — likely firmware bug (pre-1.14.1)">⚠️ hash mismatch</span>' : ''} ${statusLabel}
|
||||
<a href="#/nodes/${encodeURIComponent(n.public_key)}" class="btn-primary" style="display:inline-block;text-decoration:none;font-size:11px;padding:2px 8px;margin-left:8px">🔍 Details</a>
|
||||
<a href="#/nodes/${encodeURIComponent(n.public_key)}/analytics" class="btn-primary" style="display:inline-block;margin-left:4px;text-decoration:none;font-size:11px;padding:2px 8px">📊 Analytics</a>
|
||||
</div>
|
||||
|
||||
@@ -43,9 +43,11 @@ const crypto = require('crypto');
|
||||
const PacketStore = require('./packet-store');
|
||||
|
||||
// --- Precomputed hash_size map (updated on new packets, not per-request) ---
|
||||
const _hashSizeMap = new Map();
|
||||
const _hashSizeMap = new Map(); // pubkey → latest hash_size (number)
|
||||
const _hashSizeAllMap = new Map(); // pubkey → Set of all hash_sizes seen
|
||||
function _rebuildHashSizeMap() {
|
||||
_hashSizeMap.clear();
|
||||
_hashSizeAllMap.clear();
|
||||
// Pass 1: from ADVERT packets (most authoritative — path byte bits 7-6)
|
||||
// packets array is sorted newest-first, so first-match = newest ADVERT
|
||||
for (const p of pktStore.packets) {
|
||||
@@ -53,9 +55,12 @@ function _rebuildHashSizeMap() {
|
||||
try {
|
||||
const d = JSON.parse(p.decoded_json || '{}');
|
||||
const pk = d.pubKey || d.public_key;
|
||||
if (pk && !_hashSizeMap.has(pk)) {
|
||||
if (pk) {
|
||||
const pathByte = parseInt(p.raw_hex.slice(2, 4), 16);
|
||||
_hashSizeMap.set(pk, ((pathByte >> 6) & 0x3) + 1);
|
||||
const hs = ((pathByte >> 6) & 0x3) + 1;
|
||||
if (!_hashSizeMap.has(pk)) _hashSizeMap.set(pk, hs);
|
||||
if (!_hashSizeAllMap.has(pk)) _hashSizeAllMap.set(pk, new Set());
|
||||
_hashSizeAllMap.get(pk).add(hs);
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
@@ -89,7 +94,10 @@ function _updateHashSizeForPacket(p) {
|
||||
const pk = d.pubKey || d.public_key;
|
||||
if (pk) {
|
||||
const pathByte = parseInt(p.raw_hex.slice(2, 4), 16);
|
||||
_hashSizeMap.set(pk, ((pathByte >> 6) & 0x3) + 1);
|
||||
const hs = ((pathByte >> 6) & 0x3) + 1;
|
||||
_hashSizeMap.set(pk, hs);
|
||||
if (!_hashSizeAllMap.has(pk)) _hashSizeAllMap.set(pk, new Set());
|
||||
_hashSizeAllMap.get(pk).add(hs);
|
||||
}
|
||||
} catch {}
|
||||
} else if (p.path_json && p.decoded_json) {
|
||||
@@ -1238,6 +1246,9 @@ app.get('/api/nodes', (req, res) => {
|
||||
// Use precomputed hash_size map (rebuilt at startup, updated on new packets)
|
||||
for (const node of nodes) {
|
||||
node.hash_size = _hashSizeMap.get(node.public_key) || null;
|
||||
const allSizes = _hashSizeAllMap.get(node.public_key);
|
||||
node.hash_size_inconsistent = allSizes ? allSizes.size > 1 : false;
|
||||
if (allSizes && allSizes.size > 1) node.hash_sizes_seen = [...allSizes].sort();
|
||||
}
|
||||
|
||||
res.json({ nodes, total, counts });
|
||||
@@ -1378,6 +1389,9 @@ app.get('/api/nodes/:pubkey', (req, res) => {
|
||||
const node = db.db.prepare('SELECT * FROM nodes WHERE public_key = ?').get(pubkey);
|
||||
if (!node) return res.status(404).json({ error: 'Not found' });
|
||||
node.hash_size = _hashSizeMap.get(pubkey) || null;
|
||||
const allSizes = _hashSizeAllMap.get(pubkey);
|
||||
node.hash_size_inconsistent = allSizes ? allSizes.size > 1 : false;
|
||||
if (allSizes && allSizes.size > 1) node.hash_sizes_seen = [...allSizes].sort();
|
||||
const recentAdverts = (pktStore.byNode.get(pubkey) || []).slice(-20).reverse();
|
||||
const _nResult = { node, recentAdverts };
|
||||
cache.set(_ck, _nResult, TTL.nodeDetail);
|
||||
|
||||
Reference in New Issue
Block a user