diff --git a/src/stats.js b/src/stats.js index 6873d6e..8d12e55 100644 --- a/src/stats.js +++ b/src/stats.js @@ -1,4 +1,3 @@ -const crypto = require("crypto"); const path = require('path'); const express = require('express'); const router = express.Router(); @@ -12,67 +11,9 @@ const prisma = new PrismaClient(); const root = new protobufjs.Root(); root.resolvePath = (origin, target) => path.join(__dirname, "protos", target); root.loadSync('meshtastic/mqtt.proto'); -const Data = root.lookupType("Data"); const HardwareModel = root.lookupEnum("HardwareModel"); -const ServiceEnvelope = root.lookupType("ServiceEnvelope"); const PortNum = root.lookupEnum("PortNum"); -const decryptionKeys = [ - "1PG7OiApB1nwvP+rz05pAQ==", // add default "AQ==" decryption key -]; - -function createNonce(packetId, fromNode) { - - // Expand packetId to 64 bits - const packetId64 = BigInt(packetId); - - // Initialize block counter (32-bit, starts at zero) - const blockCounter = 0; - - // Create a buffer for the nonce - const buf = Buffer.alloc(16); - - // Write packetId, fromNode, and block counter to the buffer - buf.writeBigUInt64LE(packetId64, 0); - buf.writeUInt32LE(fromNode, 8); - buf.writeUInt32LE(blockCounter, 12); - - return buf; - -} - -function decrypt(packet) { - - // attempt to decrypt with all available decryption keys - for(const decryptionKey of decryptionKeys){ - try { - const key = Buffer.from(decryptionKey, "base64"); - const nonceBuffer = createNonce(packet.id, packet.from); - - // determine algorithm based on key length - var algorithm = null; - if(key.length === 16){ - algorithm = "aes-128-ctr"; - } else if(key.length === 32){ - algorithm = "aes-256-ctr"; - } else { - // skip this key, try the next one... - console.error(`Skipping decryption key with invalid length: ${key.length}`); - continue; - } - - const decipher = crypto.createDecipheriv(algorithm, key, nonceBuffer); - const decryptedBuffer = Buffer.concat([decipher.update(packet.encrypted), decipher.final()]); - - return Data.decode(decryptedBuffer); - - } catch(e){} - } - - // couldn't decrypt - return null; - -} router.get('/hardware-models', async (req, res) => { try { @@ -148,60 +89,7 @@ router.get('/messages-per-hour', async (req, res) => { } }); -router.get('/position-precision', async (req, res) => { - try { - const sevenDaysAgo = new Date(); - sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7); - - const result = await prisma.node.groupBy({ - by: ['position_precision'], - where: { - position_updated_at: { gte: sevenDaysAgo }, - position_precision: { not: null }, - }, - _count: { - position_precision: true, - }, - }); - - const formatted = result.map(r => ({ - position_precision: r.position_precision, - count: r._count.position_precision, - })); - - res.set('Cache-Control', 'public, max-age=600'); // 10 min cache - res.json(formatted); - - } catch (error) { - console.error('Error fetching data:', error); - res.status(500).json({ error: 'Internal Server Error' }); - } -}); - router.get('/most-active-nodes', async (req, res) => { - try { - const result = await prisma.$queryRaw` - SELECT n.long_name, COUNT(*) AS count - FROM service_envelopes se - JOIN nodes n ON se.from = n.node_id - WHERE - se.created_at >= NOW() - INTERVAL 1 DAY - AND se.mqtt_topic NOT LIKE '%/map/' - GROUP BY n.long_name - ORDER BY count DESC - LIMIT 25; - `; - - res.set('Cache-Control', 'public, max-age=600'); // 10 min cache - res.json(result); - - } catch (error) { - console.error('Error fetching data:', error); - res.status(500).json({ error: 'Internal Server Error' }); - } -}); - -router.get('/most-active-nodes2', async (req, res) => { try { const result = await prisma.$queryRaw` SELECT n.long_name, COUNT(*) AS count @@ -234,66 +122,6 @@ router.get('/portnum-counts', async (req, res) => { const now = new Date(); const startTime = new Date(now.getTime() - hours * 60 * 60 * 1000); - try { - const messages = await prisma.serviceEnvelope.findMany({ - where: { - created_at: { gte: startTime }, - ...(Number.isInteger(nodeId) ? { from: nodeId } : {}) - }, - select: { protobuf: true, mqtt_topic: true } - }); - - const counts = {}; - for (const row of messages) { - try { - // We want to filter out any map reports. - if (row.mqtt_topic && row.mqtt_topic.endsWith("/map/")) { - continue; - } - - const envelope = ServiceEnvelope.decode(row.protobuf); - const packet = envelope.packet; - - if (!packet?.encrypted) { - counts[0] = (counts[0] || 0) + 1; - continue; - } - - const dataMessage = decrypt(packet); - - if (dataMessage?.portnum !== undefined) { - const portnum = dataMessage.portnum; - counts[portnum] = (counts[portnum] || 0) + 1; - } else { - // couldn't decrypt or no portnum in decrypted message - counts[0] = (counts[0] || 0) + 1; - } - } catch (err) { - console.warn("Decode error:", err.message); - counts[0] = (counts[0] || 0) + 1; - } - } - - const result = Object.entries(counts).map(([portnum, count]) => ({ - portnum: parseInt(portnum), - count, - label: PortNum.valuesById[portnum] ?? "UNKNOWN", - })).sort((a, b) => a.portnum - b.portnum); - - res.json(result); - - } catch (err) { - console.error("Error in /portnum-counts:", err); - res.status(500).json({ message: "Internal server error" }); - } -}); - -router.get('/portnum-counts2', async (req, res) => { - const nodeId = req.query.nodeId ? parseInt(req.query.nodeId, 10) : null; - const hours = 24; - const now = new Date(); - const startTime = new Date(now.getTime() - hours * 60 * 60 * 1000); - try { const envelopes = await prisma.serviceEnvelope.findMany({ where: {