Skip to content

Commit a182db6

Browse files
fix attack timearcs error
1 parent 27f07c0 commit a182db6

5 files changed

Lines changed: 18585 additions & 13123 deletions

File tree

attack_timearcs.html

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,6 @@
6363
<div class="controls">
6464
<label for="fileInput" style="font-weight:600;">Upload CSV</label>
6565
<input id="fileInput" type="file" accept=".csv" />
66-
<label for="eventTypeInput" style="font-weight:600;">Event Types JSON</label>
67-
<input id="eventTypeInput" type="file" accept=".json" />
6866
<label for="ipMapInput" style="font-weight:600;">IP Map JSON</label>
6967
<input id="ipMapInput" type="file" accept=".json" />
7068
<fieldset style="border:1px solid #dee2e6; padding:4px 8px; border-radius:4px; display:flex; gap:8px; align-items:center;">

attack_timearcs.js

Lines changed: 185 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
(function () {
77
const fileInput = document.getElementById('fileInput');
8-
const eventTypeInput = document.getElementById('eventTypeInput');
98
const ipMapInput = document.getElementById('ipMapInput');
109
const statusEl = document.getElementById('status');
1110
const svg = d3.select('#chart');
@@ -76,27 +75,70 @@
7675
const text = await file.text();
7776
const rows = d3.csvParse(text.trim());
7877
lastRawCsvRows = rows; // cache raw rows
78+
79+
console.log('Processing CSV with IP map status:', {
80+
ipMapLoaded,
81+
ipMapSize: ipIdToAddr ? ipIdToAddr.size : 0
82+
});
83+
84+
// Warn if IP map is not loaded
85+
if (!ipMapLoaded || !ipIdToAddr || ipIdToAddr.size === 0) {
86+
console.warn('IP map not loaded or empty. Some IP IDs may not be mapped correctly.');
87+
status('Warning: IP map not loaded. Some data may be filtered out.');
88+
}
89+
7990
const data = rows.map((d, i) => {
8091
const attackName = decodeAttack(d.attack);
8192
const attackGroupName = decodeAttackGroup(d.attack_group, d.attack);
93+
const srcIp = decodeIp(d.src_ip);
94+
const dstIp = decodeIp(d.dst_ip);
8295
return {
8396
idx: i,
8497
timestamp: toNumber(d.timestamp),
8598
length: toNumber(d.length),
86-
src_ip: decodeIp(d.src_ip),
87-
dst_ip: decodeIp(d.dst_ip),
99+
src_ip: srcIp,
100+
dst_ip: dstIp,
88101
protocol: (d.protocol || '').toUpperCase() || 'OTHER',
89102
count: toNumber(d.count) || 1,
90103
attack: attackName,
91104
attack_group: attackGroupName,
92105
};
93-
}).filter(d => isFinite(d.timestamp) && d.src_ip && d.dst_ip);
106+
}).filter(d => {
107+
// Filter out records with invalid data
108+
const hasValidTimestamp = isFinite(d.timestamp);
109+
const hasValidSrcIp = d.src_ip && d.src_ip !== 'N/A' && !d.src_ip.startsWith('IP_');
110+
const hasValidDstIp = d.dst_ip && d.dst_ip !== 'N/A' && !d.dst_ip.startsWith('IP_');
111+
112+
// Debug logging for filtered records
113+
if (!hasValidSrcIp || !hasValidDstIp) {
114+
console.log('Filtering out record:', {
115+
src_ip: d.src_ip,
116+
dst_ip: d.dst_ip,
117+
hasValidSrcIp,
118+
hasValidDstIp,
119+
ipMapLoaded,
120+
ipMapSize: ipIdToAddr ? ipIdToAddr.size : 0
121+
});
122+
}
123+
124+
return hasValidTimestamp && hasValidSrcIp && hasValidDstIp;
125+
});
94126

95127
if (data.length === 0) {
96-
status('No valid rows found. Ensure CSV has required columns.');
128+
status('No valid rows found. Ensure CSV has required columns and IP mappings are available.');
97129
clearChart();
98130
return;
99131
}
132+
133+
// Report how many rows were filtered out
134+
const totalRows = rows.length;
135+
const filteredRows = totalRows - data.length;
136+
if (filteredRows > 0) {
137+
status(`Loaded ${data.length} valid rows (${filteredRows} rows filtered due to missing IP mappings)`);
138+
} else {
139+
status(`Loaded ${data.length} records`);
140+
}
141+
100142
render(data);
101143
} catch (err) {
102144
console.error(err);
@@ -105,49 +147,6 @@
105147
}
106148
});
107149

108-
// Allow user to upload a custom event_type_mapping JSON to override default mapping
109-
eventTypeInput?.addEventListener('change', async (e) => {
110-
const file = e.target.files?.[0];
111-
if (!file) return;
112-
status(`Loading event type map ${file.name} …`);
113-
try {
114-
const text = await file.text();
115-
const obj = JSON.parse(text);
116-
// Expect format name -> id OR id -> name. Detect by sampling a few keys.
117-
const entries = Object.entries(obj);
118-
const rev = new Map();
119-
if (entries.length) {
120-
// Heuristic: if value is number, we assume name->id and reverse; else if key is numeric, we assume id->name.
121-
let nameToIdMode = 0, idToNameMode = 0;
122-
for (const [k, v] of entries.slice(0, 10)) {
123-
if (typeof v === 'number') nameToIdMode++;
124-
if (!isNaN(+k) && typeof v === 'string') idToNameMode++;
125-
}
126-
if (nameToIdMode >= idToNameMode) {
127-
for (const [name, id] of entries) {
128-
const num = Number(id);
129-
if (Number.isFinite(num)) rev.set(num, name);
130-
}
131-
} else {
132-
for (const [idStr, name] of entries) {
133-
const num = Number(idStr);
134-
if (Number.isFinite(num) && typeof name === 'string') rev.set(num, name);
135-
}
136-
}
137-
}
138-
attackIdToName = rev;
139-
status(`Custom event type map loaded (${rev.size} entries). Re-rendering…`);
140-
// If a dataset was already rendered, we need to re-decode attacks and re-render.
141-
// Easier: trigger reload of default CSV if present, else wait for user CSV.
142-
// We'll look for currently loaded arcs' underlying data isn't stored; keep a cached last CSV rows.
143-
if (lastRawCsvRows) {
144-
render(rebuildDataFromRawRows(lastRawCsvRows));
145-
}
146-
} catch (err) {
147-
console.error(err);
148-
status('Failed to parse event type JSON.');
149-
}
150-
});
151150

152151
// Allow user to upload a custom ip_map JSON (expected format: { "1.2.3.4": 123, ... } OR reverse { "123": "1.2.3.4" })
153152
ipMapInput?.addEventListener('change', async (e) => {
@@ -180,6 +179,8 @@
180179
}
181180
ipIdToAddr = rev;
182181
ipMapLoaded = true;
182+
console.log(`Custom IP map loaded with ${rev.size} entries`);
183+
console.log('Sample entries:', Array.from(rev.entries()).slice(0, 5));
183184
status(`Custom IP map loaded (${rev.size} entries). Re-rendering…`);
184185
if (lastRawCsvRows) {
185186
// rebuild to decode IP ids again
@@ -209,7 +210,13 @@
209210
attack: attackName,
210211
attack_group: attackGroupName,
211212
};
212-
}).filter(d => isFinite(d.timestamp) && d.src_ip && d.dst_ip);
213+
}).filter(d => {
214+
// Filter out records with invalid data
215+
const hasValidTimestamp = isFinite(d.timestamp);
216+
const hasValidSrcIp = d.src_ip && d.src_ip !== 'N/A' && !d.src_ip.startsWith('IP_');
217+
const hasValidDstIp = d.dst_ip && d.dst_ip !== 'N/A' && !d.dst_ip.startsWith('IP_');
218+
return hasValidTimestamp && hasValidSrcIp && hasValidDstIp;
219+
});
213220
}
214221

215222
async function tryLoadDefaultCsv() {
@@ -234,10 +241,28 @@
234241
attack: attackName,
235242
attack_group: attackGroupName,
236243
};
237-
}).filter(d => isFinite(d.timestamp) && d.src_ip && d.dst_ip);
244+
}).filter(d => {
245+
// Filter out records with invalid data
246+
const hasValidTimestamp = isFinite(d.timestamp);
247+
const hasValidSrcIp = d.src_ip && d.src_ip !== 'N/A' && !d.src_ip.startsWith('IP_');
248+
const hasValidDstIp = d.dst_ip && d.dst_ip !== 'N/A' && !d.dst_ip.startsWith('IP_');
249+
return hasValidTimestamp && hasValidSrcIp && hasValidDstIp;
250+
});
238251

239-
if (!data.length) return;
240-
status(`Loaded default: 90min_day1_attacks.csv (${data.length} rows)`);
252+
if (!data.length) {
253+
status('Default CSV loaded but no valid rows found. Check IP mappings.');
254+
return;
255+
}
256+
257+
// Report how many rows were filtered out
258+
const totalRows = rows.length;
259+
const filteredRows = totalRows - data.length;
260+
if (filteredRows > 0) {
261+
status(`Loaded default: 90min_day1_attacks.csv (${data.length} valid rows, ${filteredRows} filtered due to missing IP mappings)`);
262+
} else {
263+
status(`Loaded default: 90min_day1_attacks.csv (${data.length} rows)`);
264+
}
265+
241266
render(data);
242267
} catch (err) {
243268
// ignore if file isn't present; keep waiting for upload
@@ -279,16 +304,65 @@
279304
// Determine timestamp handling
280305
const tsMin = d3.min(data, d => d.timestamp);
281306
const tsMax = d3.max(data, d => d.timestamp);
307+
// Check if timestamps are in milliseconds (very large numbers) or minutes
308+
const looksLikeMilliseconds = tsMin > 1e12; // heuristic: milliseconds since epoch
282309
const looksAbsolute = tsMin > 1e6; // heuristic: minutes since epoch
283310
const base = looksAbsolute ? 0 : tsMin; // for relative minutes, normalize to 0
311+
312+
console.log('Timestamp debug:', {
313+
tsMin,
314+
tsMax,
315+
looksLikeMilliseconds,
316+
looksAbsolute,
317+
base,
318+
sampleTimestamps: data.slice(0, 5).map(d => d.timestamp)
319+
});
284320

285-
const toDate = (m) => new Date((looksAbsolute ? m : (m - base)) * 60_000);
321+
const toDate = (m) => {
322+
if (m === undefined || m === null || !isFinite(m)) {
323+
console.warn('Invalid timestamp in toDate:', m);
324+
return new Date(0); // Return epoch as fallback
325+
}
326+
327+
let result;
328+
if (looksLikeMilliseconds) {
329+
// Timestamp is already in milliseconds
330+
result = new Date(m);
331+
} else if (looksAbsolute) {
332+
// Timestamp is in minutes since epoch
333+
result = new Date(m * 60_000);
334+
} else {
335+
// Timestamp is relative minutes
336+
result = new Date((m - base) * 60_000);
337+
}
338+
339+
if (!isFinite(result.getTime())) {
340+
console.warn('Invalid date result in toDate:', {
341+
m,
342+
looksLikeMilliseconds,
343+
looksAbsolute,
344+
base,
345+
result
346+
});
347+
return new Date(0); // Return epoch as fallback
348+
}
349+
return result;
350+
};
286351

287352
// Aggregate links; then order IPs using the React component's approach:
288353
// primary-attack grouping, groups ordered by earliest time, nodes within group by force-simulated y
289354
const links = computeLinks(data); // aggregated per pair per minute
290355
const nodes = computeNodesByAttackGrouping(links);
291356
const ips = nodes.map(n => n.name);
357+
358+
console.log('Render debug:', {
359+
dataLength: data.length,
360+
linksLength: links.length,
361+
nodesLength: nodes.length,
362+
ipsLength: ips.length,
363+
sampleIps: ips.slice(0, 5),
364+
sampleLinks: links.slice(0, 3)
365+
});
292366
// Determine which label dimension we use (attack vs group) for legend and coloring
293367
const activeLabelKey = labelMode === 'attack_group' ? 'attack_group' : 'attack';
294368
const attacks = Array.from(new Set(links.map(l => l[activeLabelKey] || 'normal'))).sort();
@@ -300,14 +374,32 @@
300374
height = margin.top + innerHeight + margin.bottom;
301375
svg.attr('width', width).attr('height', height);
302376

377+
const xMinDate = toDate(tsMin);
378+
const xMaxDate = toDate(tsMax);
379+
380+
console.log('X-scale debug:', {
381+
tsMin,
382+
tsMax,
383+
xMinDate,
384+
xMaxDate,
385+
xMinValid: isFinite(xMinDate.getTime()),
386+
xMaxValid: isFinite(xMaxDate.getTime())
387+
});
388+
303389
const x = d3.scaleTime()
304-
.domain([toDate(tsMin), toDate(tsMax)])
390+
.domain([xMinDate, xMaxDate])
305391
.range([margin.left, width - margin.right]);
306392

307393
const y = d3.scalePoint()
308394
.domain(ips)
309395
.range([margin.top, margin.top + innerHeight])
310396
.padding(0.5);
397+
398+
console.log('Y-scale debug:', {
399+
domain: ips,
400+
domainLength: ips.length,
401+
sampleYValues: ips.slice(0, 5).map(ip => ({ ip, y: y(ip) }))
402+
});
311403

312404
// Compute a right-side padding so the largest arc does not get clipped.
313405
// The horizontal reach of an arc equals its radius = |y2 - y1|/2.
@@ -391,6 +483,11 @@
391483

392484
// Arc path generator between two points sharing same x
393485
function verticalArcPath(xp, y1, y2) {
486+
// Validate inputs to prevent undefined values in SVG path
487+
if (xp === undefined || y1 === undefined || y2 === undefined) {
488+
console.warn('Invalid arc path parameters:', { xp, y1, y2 });
489+
return 'M0,0 L0,0'; // Return a minimal valid path
490+
}
394491
const yTop = Math.min(y1, y2);
395492
const yBot = Math.max(y1, y2);
396493
const dr = Math.max(1, (yBot - yTop) / 2);
@@ -410,9 +507,39 @@
410507
.attr('stroke', d => colorForAttack((labelMode==='attack_group'? d.attack_group : d.attack) || 'normal'))
411508
.attr('stroke-width', d => widthScale(Math.max(1, d.count)))
412509
.attr('d', d => {
413-
const xp = x(toDate(d.minute));
510+
const dateFromMinute = toDate(d.minute);
511+
const xp = x(dateFromMinute);
414512
const y1 = y(d.source);
415513
const y2 = y(d.target);
514+
515+
// Validate that x-scale returned valid values
516+
if (xp === undefined || !isFinite(xp)) {
517+
console.warn('Invalid x-coordinate for arc:', {
518+
minute: d.minute,
519+
dateFromMinute,
520+
xp,
521+
xDomain: [xMinDate, xMaxDate],
522+
xRange: [margin.left, width - margin.right]
523+
});
524+
return 'M0,0 L0,0'; // Return minimal valid path
525+
}
526+
527+
// Validate that y-scale returned valid values
528+
if (y1 === undefined || y2 === undefined) {
529+
console.warn('Invalid y-coordinates for arc:', {
530+
source: d.source,
531+
target: d.target,
532+
y1,
533+
y2,
534+
xp,
535+
minute: d.minute,
536+
yDomain: ips,
537+
sourceInDomain: ips.includes(d.source),
538+
targetInDomain: ips.includes(d.target)
539+
});
540+
return 'M0,0 L0,0'; // Return minimal valid path
541+
}
542+
416543
return verticalArcPath(xp, y1, y2);
417544
})
418545
.on('mouseover', function (event, d) {
@@ -717,6 +844,9 @@
717844
if (Number.isFinite(n) && ipIdToAddr) {
718845
const ip = ipIdToAddr.get(n);
719846
if (ip) return ip;
847+
// If IP ID not found in map, log it and return a placeholder
848+
console.warn(`IP ID ${n} not found in mapping. Available IDs: ${ipIdToAddr ? ipIdToAddr.size : 0} entries`);
849+
return `IP_${n}`;
720850
}
721851
return v; // fallback to original string
722852
}

0 commit comments

Comments
 (0)