import { isPrivateIp } from '@libp2p/utils/private-ip';
import { trackedMap } from '@libp2p/utils/tracked-map';
import { multiaddr, protocols } from '@multiformats/multiaddr';
const MAX_DATE = 8_640_000_000_000_000;
export const defaultValues = {
    maxObservedAddresses: 10
};
const CODEC_TLS = 0x01c0;
const CODEC_SNI = 0x01c1;
const CODEC_DNS = 0x35;
const CODEC_DNS4 = 0x36;
const CODEC_DNS6 = 0x37;
const CODEC_DNSADDR = 0x38;
export class DNSMappings {
    log;
    mappings;
    constructor(components, init = {}) {
        this.log = components.logger.forComponent('libp2p:address-manager:dns-mappings');
        this.mappings = trackedMap({
            name: 'libp2p_address_manager_dns_mappings',
            metrics: components.metrics
        });
    }
    has(ma) {
        const host = this.findHost(ma);
        for (const mapping of this.mappings.values()) {
            if (mapping.domain === host) {
                return true;
            }
        }
        return false;
    }
    add(domain, addresses) {
        addresses.forEach(ip => {
            this.log('add DNS mapping %s to %s', ip, domain);
            // we are only confident if this is an local domain mapping, otherwise
            // we will require external validation
            const verified = isPrivateIp(ip) === true;
            this.mappings.set(ip, {
                domain,
                verified,
                expires: verified ? MAX_DATE - Date.now() : 0,
                lastVerified: verified ? MAX_DATE - Date.now() : undefined
            });
        });
    }
    remove(ma) {
        const host = this.findHost(ma);
        let wasConfident = false;
        for (const [ip, mapping] of this.mappings.entries()) {
            if (mapping.domain === host) {
                this.log('removing %s to %s DNS mapping %e', ip, mapping.domain);
                this.mappings.delete(ip);
                wasConfident = wasConfident || mapping.verified;
            }
        }
        return wasConfident;
    }
    getAll(addresses) {
        const dnsMappedAddresses = [];
        for (let i = 0; i < addresses.length; i++) {
            const address = addresses[i];
            const tuples = address.multiaddr.stringTuples();
            const host = tuples[0][1];
            if (host == null) {
                continue;
            }
            for (const [ip, mapping] of this.mappings.entries()) {
                if (host !== ip) {
                    continue;
                }
                // insert SNI tuple after TLS tuple, if one is present
                const mappedIp = this.maybeAddSNITuple(tuples, mapping.domain);
                if (mappedIp) {
                    // remove the address and replace it with the version that includes
                    // the SNI tuple
                    addresses.splice(i, 1);
                    i--;
                    dnsMappedAddresses.push({
                        multiaddr: multiaddr(`/${tuples.map(tuple => {
                            return [
                                protocols(tuple[0]).name,
                                tuple[1]
                            ].join('/');
                        }).join('/')}`),
                        verified: mapping.verified,
                        type: 'dns-mapping',
                        expires: mapping.expires,
                        lastVerified: mapping.lastVerified
                    });
                }
            }
        }
        return dnsMappedAddresses;
    }
    maybeAddSNITuple(tuples, domain) {
        for (let j = 0; j < tuples.length; j++) {
            if (tuples[j][0] === CODEC_TLS && tuples[j + 1]?.[0] !== CODEC_SNI) {
                tuples.splice(j + 1, 0, [CODEC_SNI, domain]);
                return true;
            }
        }
        return false;
    }
    confirm(ma, ttl) {
        const host = this.findHost(ma);
        let startingConfidence = false;
        for (const [ip, mapping] of this.mappings.entries()) {
            if (mapping.domain === host) {
                this.log('marking %s to %s DNS mapping as verified', ip, mapping.domain);
                startingConfidence = mapping.verified;
                mapping.verified = true;
                mapping.expires = Date.now() + ttl;
                mapping.lastVerified = Date.now();
            }
        }
        return startingConfidence;
    }
    unconfirm(ma, ttl) {
        const host = this.findHost(ma);
        let wasConfident = false;
        for (const [ip, mapping] of this.mappings.entries()) {
            if (mapping.domain === host) {
                this.log('removing verification of %s to %s DNS mapping', ip, mapping.domain);
                wasConfident = wasConfident || mapping.verified;
                mapping.verified = false;
                mapping.expires = Date.now() + ttl;
            }
        }
        return wasConfident;
    }
    findHost(ma) {
        for (const tuple of ma.stringTuples()) {
            if (tuple[0] === CODEC_SNI) {
                return tuple[1];
            }
            if (tuple[0] === CODEC_DNS || tuple[0] === CODEC_DNS4 || tuple[0] === CODEC_DNS6 || tuple[0] === CODEC_DNSADDR) {
                return tuple[1];
            }
        }
    }
}
//# sourceMappingURL=dns-mappings.js.map