const { fetchRipeStatCached } = require("./services/ripe-stat"); // Feature 25: Topology / AS-Relationships async function fetchTopology(targetAsn, depth) { const maxDepth = Math.min(depth || 2, 3); const nodes = new Map(); const edges = []; async function fetchNeighboursForAsn(asn, currentDepth) { if (nodes.has(asn) && nodes.get(asn).depth <= currentDepth) return; const [data, overview] = await Promise.all([ fetchRipeStatCached("https://stat.ripe.net/data/asn-neighbours/data.json?resource=AS" + asn), fetchRipeStatCached("https://stat.ripe.net/data/as-overview/data.json?resource=AS" + asn), ]); const name = overview?.data?.holder || ""; const neighbours = data?.data?.neighbours || []; const upstreams = neighbours.filter((n) => n.type === "left"); const downstreams = neighbours.filter((n) => n.type === "right"); const peers = neighbours.filter((n) => n.type === "uncertain" || n.type === "peer"); const nodeType = asn === targetAsn ? "target" : currentDepth === 1 ? "direct" : "indirect"; nodes.set(asn, { asn, name, type: nodeType, depth: currentDepth }); upstreams.forEach((n) => { if (!nodes.has(n.asn)) nodes.set(n.asn, { asn: n.asn, name: n.as_name || "", type: "upstream", depth: currentDepth + 1 }); edges.push({ from: n.asn, to: asn, relationship: "provider-to-customer" }); }); downstreams.forEach((n) => { if (!nodes.has(n.asn)) nodes.set(n.asn, { asn: n.asn, name: n.as_name || "", type: "downstream", depth: currentDepth + 1 }); edges.push({ from: asn, to: n.asn, relationship: "provider-to-customer" }); }); peers.slice(0, 10).forEach((n) => { if (!nodes.has(n.asn)) nodes.set(n.asn, { asn: n.asn, name: n.as_name || "", type: "peer", depth: currentDepth + 1 }); edges.push({ from: asn, to: n.asn, relationship: "peer" }); }); if (currentDepth < maxDepth && upstreams.length > 0) { const top5 = upstreams.sort((a, b) => (b.power || 0) - (a.power || 0)).slice(0, 5); await Promise.all(top5.map((u) => fetchNeighboursForAsn(u.asn, currentDepth + 1))); } } await fetchNeighboursForAsn(targetAsn, 0); const edgeSet = new Set(); const uniqueEdges = edges.filter((e) => { const key = e.from + "-" + e.to + "-" + e.relationship; if (edgeSet.has(key)) return false; edgeSet.add(key); return true; }); return { nodes: [...nodes.values()], edges: uniqueEdges, target_asn: targetAsn, depth: maxDepth }; } module.exports = { fetchTopology };