export class QemuIcmpTracker {
    icmpTimings = new Map();
    icmpDebugBuffer = Buffer.alloc(0);
    icmpRxBuffer = Buffer.alloc(0);
    emitDebug;
    getEventLoopDelay;
    constructor(emitDebug, getEventLoopDelay) {
        this.emitDebug = emitDebug;
        this.getEventLoopDelay = getEventLoopDelay;
    }
    reset() {
        this.icmpTimings.clear();
        this.icmpDebugBuffer = Buffer.alloc(0);
        this.icmpRxBuffer = Buffer.alloc(0);
    }
    recordIcmpTiming(info) {
        const key = this.icmpKey(info.srcIP, info.dstIP, info.id, info.seq);
        const existing = this.icmpTimings.get(key);
        if (existing) {
            if (Number.isFinite(info.recvTime) && info.recvTime > 0) {
                existing.recvTime = info.recvTime;
            }
            if (Number.isFinite(info.rxTime) && info.rxTime > 0) {
                existing.rxTime = info.rxTime;
            }
            if (Number.isFinite(info.replyTime) && info.replyTime > 0) {
                existing.replyTime = info.replyTime;
            }
            if (Number.isFinite(info.size) && info.size > 0) {
                existing.size = info.size;
            }
            existing.srcIP = info.srcIP;
            existing.dstIP = info.dstIP;
            return;
        }
        this.icmpTimings.set(key, info);
    }
    trackIcmpRequests(chunk, now) {
        this.icmpRxBuffer = Buffer.concat([this.icmpRxBuffer, chunk]);
        while (this.icmpRxBuffer.length >= 4) {
            const frameLen = this.icmpRxBuffer.readUInt32BE(0);
            if (this.icmpRxBuffer.length < 4 + frameLen)
                break;
            const frame = this.icmpRxBuffer.subarray(4, 4 + frameLen);
            this.icmpRxBuffer = this.icmpRxBuffer.subarray(4 + frameLen);
            this.logIcmpRequestFrame(frame, now);
        }
    }
    trackIcmpReplies(chunk, now) {
        this.icmpDebugBuffer = Buffer.concat([this.icmpDebugBuffer, chunk]);
        while (this.icmpDebugBuffer.length >= 4) {
            const frameLen = this.icmpDebugBuffer.readUInt32BE(0);
            if (this.icmpDebugBuffer.length < 4 + frameLen)
                break;
            const frame = this.icmpDebugBuffer.subarray(4, 4 + frameLen);
            this.icmpDebugBuffer = this.icmpDebugBuffer.subarray(4 + frameLen);
            this.logIcmpReplyFrame(frame, now);
        }
    }
    icmpKey(srcIP, dstIP, id, seq) {
        return `${id}:${seq}:${srcIP}:${dstIP}`;
    }
    logIcmpRequestFrame(frame, now) {
        if (frame.length < 14)
            return;
        const etherType = frame.readUInt16BE(12);
        if (etherType !== 0x0800)
            return;
        const ip = frame.subarray(14);
        if (ip.length < 20)
            return;
        const version = ip[0] >> 4;
        if (version !== 4)
            return;
        const headerLen = (ip[0] & 0x0f) * 4;
        if (ip.length < headerLen)
            return;
        if (ip[9] !== 1)
            return;
        const totalLen = ip.readUInt16BE(2);
        const payloadEnd = Math.min(ip.length, totalLen);
        if (payloadEnd <= headerLen)
            return;
        const icmp = ip.subarray(headerLen, payloadEnd);
        if (icmp.length < 8)
            return;
        if (icmp[0] !== 8)
            return;
        const srcIP = `${ip[12]}.${ip[13]}.${ip[14]}.${ip[15]}`;
        const dstIP = `${ip[16]}.${ip[17]}.${ip[18]}.${ip[19]}`;
        const id = icmp.readUInt16BE(4);
        const seq = icmp.readUInt16BE(6);
        this.recordIcmpTiming({
            srcIP,
            dstIP,
            id,
            seq,
            recvTime: now,
            rxTime: now,
            replyTime: now,
            size: icmp.length,
        });
    }
    logIcmpReplyFrame(frame, now) {
        if (frame.length < 14)
            return;
        const etherType = frame.readUInt16BE(12);
        if (etherType !== 0x0800)
            return;
        const ip = frame.subarray(14);
        if (ip.length < 20)
            return;
        const version = ip[0] >> 4;
        if (version !== 4)
            return;
        const headerLen = (ip[0] & 0x0f) * 4;
        if (ip.length < headerLen)
            return;
        if (ip[9] !== 1)
            return;
        const totalLen = ip.readUInt16BE(2);
        const payloadEnd = Math.min(ip.length, totalLen);
        if (payloadEnd <= headerLen)
            return;
        const icmp = ip.subarray(headerLen, payloadEnd);
        if (icmp.length < 8)
            return;
        if (icmp[0] !== 0)
            return;
        const srcIP = `${ip[12]}.${ip[13]}.${ip[14]}.${ip[15]}`;
        const dstIP = `${ip[16]}.${ip[17]}.${ip[18]}.${ip[19]}`;
        const id = icmp.readUInt16BE(4);
        const seq = icmp.readUInt16BE(6);
        const key = this.icmpKey(dstIP, srcIP, id, seq);
        const timing = this.icmpTimings.get(key);
        if (!timing)
            return;
        this.icmpTimings.delete(key);
        const processingMs = timing.replyTime - timing.rxTime;
        const queuedMs = now - timing.replyTime;
        const totalMs = now - timing.rxTime;
        const guestToHostMs = Number.isFinite(timing.recvTime)
            ? timing.rxTime - timing.recvTime
            : Number.NaN;
        let eventLoopInfo = "";
        const eventLoopDelay = this.getEventLoopDelay();
        if (eventLoopDelay) {
            const meanMs = eventLoopDelay.mean / 1e6;
            const maxMs = eventLoopDelay.max / 1e6;
            eventLoopInfo = ` evloop_mean=${meanMs.toFixed(3)}ms evloop_max=${maxMs.toFixed(3)}ms`;
            eventLoopDelay.reset();
        }
        const guestToHostLabel = Number.isFinite(guestToHostMs)
            ? `guest_to_host=${guestToHostMs.toFixed(3)}ms `
            : "";
        this.emitDebug(`icmp echo id=${timing.id} seq=${timing.seq} ${timing.srcIP} -> ${timing.dstIP} size=${timing.size} ` +
            `${guestToHostLabel}processing=${processingMs.toFixed(3)}ms ` +
            `queued=${queuedMs.toFixed(3)}ms total=${totalMs.toFixed(3)}ms${eventLoopInfo}`);
    }
}
//# sourceMappingURL=icmp.js.map