"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeNetworkHost = normalizeNetworkHost;
exports.isLanHttpsHost = isLanHttpsHost;
exports.resolveHttpUrlHostForBindHost = resolveHttpUrlHostForBindHost;
exports.resolveLanHttpsHostsForBindHost = resolveLanHttpsHostsForBindHost;
exports.resolveDefaultLanHttpsHosts = resolveDefaultLanHttpsHosts;
const dgram = __importStar(require("node:dgram"));
const net = __importStar(require("node:net"));
const os = __importStar(require("node:os"));
const log_1 = require("../log");
const UDP_ROUTE_PROBE_TARGETS = {
    ipv4: { host: '192.0.2.1', port: 443 },
    ipv6: { host: '2001:db8::1', port: 443 },
};
const log = (0, log_1.createLogger)('network-hosts');
function normalizeNetworkHost(host) {
    const trimmed = host.trim();
    const unwrapped = trimmed.startsWith('[') && trimmed.endsWith(']') ? trimmed.slice(1, -1) : trimmed;
    const withoutZone = stripIpv6ZoneId(unwrapped);
    const mappedIpv4 = parseIpv4MappedIpv6Host(withoutZone.toLowerCase());
    return mappedIpv4 ?? withoutZone;
}
function isLanHttpsHost(host) {
    const normalizedHost = normalizeNetworkHost(host).toLowerCase();
    if (normalizedHost === '' || normalizedHost === 'localhost' || normalizedHost === 'loopback') {
        return false;
    }
    const ipVersion = net.isIP(normalizedHost);
    if (ipVersion === 4) {
        return (normalizedHost !== '0.0.0.0' &&
            !normalizedHost.startsWith('127.') &&
            !isIpv4LinkLocalHost(normalizedHost));
    }
    if (ipVersion === 6) {
        const mappedIpv4 = parseIpv4MappedIpv6Host(normalizedHost);
        if (mappedIpv4 !== null) {
            return isLanHttpsHost(mappedIpv4);
        }
        return (normalizedHost !== '::' &&
            normalizedHost !== '0:0:0:0:0:0:0:0' &&
            normalizedHost !== '::1' &&
            normalizedHost !== '0:0:0:0:0:0:0:1' &&
            !isIpv6LinkLocalHost(normalizedHost));
    }
    return true;
}
function resolveHttpUrlHostForBindHost(bindHost) {
    const normalizedHost = normalizeNetworkHost(bindHost);
    if (normalizedHost === '0.0.0.0' ||
        normalizedHost === '::' ||
        normalizedHost === '0:0:0:0:0:0:0:0') {
        return 'localhost';
    }
    return normalizedHost;
}
async function resolveLanHttpsHostsForBindHost(bindHost) {
    const normalizedHost = normalizeNetworkHost(bindHost);
    log.debug('Resolving LAN HTTPS certificate hosts', undefined, {
        bindHost,
        normalizedHost,
    });
    if (normalizedHost === '0.0.0.0') {
        return resolveBindAllLanHttpsHosts({
            bindHost: normalizedHost,
            families: ['ipv4'],
        });
    }
    if (normalizedHost === '::' || normalizedHost === '0:0:0:0:0:0:0:0') {
        return resolveBindAllLanHttpsHosts({
            bindHost: normalizedHost,
            families: ['ipv6', 'ipv4'],
        });
    }
    const accepted = isLanHttpsHost(normalizedHost);
    log.info('Resolved LAN HTTPS certificate hosts', undefined, {
        bindHost,
        normalizedHost,
        acceptedHosts: accepted ? [normalizedHost] : [],
    });
    return accepted ? [normalizedHost] : [];
}
async function resolveDefaultLanHttpsHosts() {
    return await resolveLanHttpsHostsForBindHost('::');
}
async function resolveBindAllLanHttpsHosts(params) {
    const rawCandidates = [];
    for (const family of params.families) {
        rawCandidates.push(...(await getUdpRouteLocalHosts(family)));
    }
    for (const family of params.families) {
        rawCandidates.push(...getNetworkInterfaceHosts(family));
    }
    const hostname = os.hostname();
    const hostnameAccepted = isDetectedHostnameCandidate(hostname);
    log.debug('LAN HTTPS host detection hostname candidate', undefined, {
        method: 'os.hostname',
        host: hostname,
        acceptedCandidate: hostnameAccepted,
    });
    if (hostnameAccepted) {
        rawCandidates.push(hostname);
    }
    const uniqueCandidates = uniqueHosts(rawCandidates);
    const acceptedHosts = [];
    const rejectedHosts = [];
    for (const host of uniqueCandidates) {
        if (isLanHttpsHost(host)) {
            acceptedHosts.push(host);
        }
        else {
            rejectedHosts.push(host);
        }
    }
    log.info('Resolved LAN HTTPS certificate hosts', undefined, {
        bindHost: params.bindHost,
        families: params.families,
        acceptedHosts,
    });
    log.debug('Resolved bind-all LAN HTTPS certificate host diagnostics', undefined, {
        bindHost: params.bindHost,
        families: params.families,
        rawCandidates,
        uniqueCandidates,
        acceptedHosts,
        rejectedHosts,
    });
    return acceptedHosts;
}
async function getUdpRouteLocalHosts(kind) {
    const host = await getUdpRouteLocalHost(kind);
    return host === null ? [] : [host];
}
async function getUdpRouteLocalHost(kind) {
    return await new Promise((resolve) => {
        const target = UDP_ROUTE_PROBE_TARGETS[kind];
        log.debug('LAN HTTPS host detection UDP route probe start', undefined, {
            method: 'udp_route_local_address_probe',
            family: kind,
            targetHost: target.host,
            targetPort: target.port,
        });
        const socket = dgram.createSocket(kind === 'ipv4' ? 'udp4' : 'udp6');
        let settled = false;
        const finish = (host, reason) => {
            if (settled)
                return;
            settled = true;
            socket.removeAllListeners('error');
            socket.close();
            const normalizedHost = host === null ? null : normalizeNetworkHost(host);
            log.debug('LAN HTTPS host detection UDP route probe finish', undefined, {
                method: 'udp_route_local_address_probe',
                family: kind,
                reason,
                host,
                normalizedHost,
            });
            resolve(normalizedHost);
        };
        socket.once('error', (error) => {
            log.debug('LAN HTTPS host detection UDP route probe error', error, {
                method: 'udp_route_local_address_probe',
                family: kind,
            });
            finish(null, 'error');
        });
        socket.connect(target.port, target.host, () => {
            const address = socket.address();
            if (typeof address === 'string') {
                finish(null, 'non_ip_socket_address');
                return;
            }
            finish(address.address, 'connected');
        });
    });
}
function getNetworkInterfaceHosts(kind) {
    const hosts = [];
    const families = os.networkInterfaces();
    const inspectedInterfaces = [];
    for (const [name, entries] of Object.entries(families)) {
        if (entries === undefined)
            continue;
        for (const entry of entries) {
            const acceptedFamily = !entry.internal &&
                ((kind === 'ipv4' && entry.family === 'IPv4') ||
                    (kind === 'ipv6' && entry.family === 'IPv6'));
            inspectedInterfaces.push({
                name,
                address: entry.address,
                family: entry.family,
                internal: entry.internal,
                acceptedFamily,
            });
            if (acceptedFamily && kind === 'ipv4') {
                hosts.push(entry.address);
            }
            if (acceptedFamily && kind === 'ipv6') {
                hosts.push(stripIpv6ZoneId(entry.address));
            }
        }
    }
    log.debug('LAN HTTPS host detection networkInterfaces result', undefined, {
        method: 'os.networkInterfaces',
        family: kind,
        hosts,
        inspectedInterfaces,
    });
    return hosts;
}
function isDetectedHostnameCandidate(host) {
    const normalizedHost = normalizeNetworkHost(host);
    if (!isLanHttpsHost(normalizedHost))
        return false;
    if (net.isIP(normalizedHost) !== 0)
        return true;
    return normalizedHost.includes('.');
}
function uniqueHosts(rawHosts) {
    const hosts = [];
    const seen = new Set();
    for (const rawHost of rawHosts) {
        const host = normalizeNetworkHost(rawHost);
        if (host === '')
            continue;
        const key = host.toLowerCase();
        if (seen.has(key))
            continue;
        seen.add(key);
        hosts.push(host);
    }
    return hosts;
}
function stripIpv6ZoneId(address) {
    const zoneIndex = address.indexOf('%');
    return zoneIndex === -1 ? address : address.slice(0, zoneIndex);
}
function isIpv4LinkLocalHost(host) {
    return host.startsWith('169.254.');
}
function isIpv6LinkLocalHost(host) {
    return (host.startsWith('fe8') ||
        host.startsWith('fe9') ||
        host.startsWith('fea') ||
        host.startsWith('feb'));
}
function parseIpv4MappedIpv6Host(host) {
    const dottedQuadIndex = host.lastIndexOf(':');
    if (dottedQuadIndex === -1)
        return null;
    const dottedQuad = host.slice(dottedQuadIndex + 1);
    if (net.isIP(dottedQuad) !== 4)
        return null;
    const prefix = host.slice(0, dottedQuadIndex).toLowerCase();
    return prefix === '::ffff' || prefix === '0:0:0:0:0:ffff' ? dottedQuad : null;
}
