import { isIPv4 } from '@chainsafe/is-ip';
import { trackedMap } from '@libp2p/utils/tracked-map';
import { multiaddr, protocols } from '@multiformats/multiaddr';
export const defaultValues = {
    maxObservedAddresses: 10
};
const CODEC_IP4 = 0x04;
const CODEC_IP6 = 0x29;
const CODEC_TCP = 0x06;
const CODEC_UDP = 0x0111;
export class IPMappings {
    log;
    mappings;
    constructor(components, init = {}) {
        this.log = components.logger.forComponent('libp2p:address-manager:ip-mappings');
        this.mappings = trackedMap({
            name: 'libp2p_address_manager_ip_mappings',
            metrics: components.metrics
        });
    }
    has(ma) {
        const tuples = ma.stringTuples();
        for (const mappings of this.mappings.values()) {
            for (const mapping of mappings) {
                if (mapping.externalIp === tuples[0][1]) {
                    return true;
                }
            }
        }
        return false;
    }
    add(internalIp, internalPort, externalIp, externalPort = internalPort, protocol = 'tcp') {
        const key = `${internalIp}-${internalPort}-${protocol}`;
        const mappings = this.mappings.get(key) ?? [];
        const mapping = {
            internalIp,
            internalPort,
            externalIp,
            externalPort,
            externalFamily: isIPv4(externalIp) ? 4 : 6,
            protocol,
            verified: false,
            expires: 0
        };
        mappings.push(mapping);
        this.mappings.set(key, mappings);
    }
    remove(ma) {
        const tuples = ma.stringTuples();
        const host = tuples[0][1] ?? '';
        const protocol = tuples[1][0] === CODEC_TCP ? 'tcp' : 'udp';
        const port = parseInt(tuples[1][1] ?? '0');
        let wasConfident = false;
        for (const [key, mappings] of this.mappings.entries()) {
            for (let i = 0; i < mappings.length; i++) {
                const mapping = mappings[i];
                if (mapping.externalIp === host && mapping.externalPort === port && mapping.protocol === protocol) {
                    this.log('removing %s:%s to %s:%s %s IP mapping', mapping.externalIp, mapping.externalPort, host, port, protocol);
                    wasConfident = wasConfident || mapping.verified;
                    mappings.splice(i, 1);
                    i--;
                }
            }
            if (mappings.length === 0) {
                this.mappings.delete(key);
            }
        }
        return wasConfident;
    }
    getAll(addresses) {
        const ipMappedAddresses = [];
        for (const { multiaddr: ma } of addresses) {
            const tuples = ma.stringTuples();
            let tuple;
            // see if the internal host/port/protocol tuple has been mapped externally
            if ((tuples[0][0] === CODEC_IP4 || tuples[0][0] === CODEC_IP6) && tuples[1][0] === CODEC_TCP) {
                tuple = `${tuples[0][1]}-${tuples[1][1]}-tcp`;
            }
            else if ((tuples[0][0] === CODEC_IP4 || tuples[0][0] === CODEC_IP6) && tuples[1][0] === CODEC_UDP) {
                tuple = `${tuples[0][1]}-${tuples[1][1]}-udp`;
            }
            if (tuple == null) {
                continue;
            }
            const mappings = this.mappings.get(tuple);
            if (mappings == null) {
                continue;
            }
            for (const mapping of mappings) {
                tuples[0][0] = mapping.externalFamily === 4 ? CODEC_IP4 : CODEC_IP6;
                tuples[0][1] = mapping.externalIp;
                tuples[1][1] = `${mapping.externalPort}`;
                ipMappedAddresses.push({
                    multiaddr: multiaddr(`/${tuples.map(tuple => {
                        return [
                            protocols(tuple[0]).name,
                            tuple[1]
                        ].join('/');
                    }).join('/')}`),
                    verified: mapping.verified,
                    type: 'ip-mapping',
                    expires: mapping.expires,
                    lastVerified: mapping.lastVerified
                });
            }
        }
        return ipMappedAddresses;
    }
    confirm(ma, ttl) {
        const tuples = ma.stringTuples();
        const host = tuples[0][1];
        let startingConfidence = false;
        for (const mappings of this.mappings.values()) {
            for (const mapping of mappings) {
                if (mapping.externalIp === host) {
                    this.log('marking %s to %s IP mapping as verified', mapping.internalIp, mapping.externalIp);
                    startingConfidence = mapping.verified;
                    mapping.verified = true;
                    mapping.expires = Date.now() + ttl;
                    mapping.lastVerified = Date.now();
                }
            }
        }
        return startingConfidence;
    }
    unconfirm(ma, ttl) {
        const tuples = ma.stringTuples();
        const host = tuples[0][1] ?? '';
        const protocol = tuples[1][0] === CODEC_TCP ? 'tcp' : 'udp';
        const port = parseInt(tuples[1][1] ?? '0');
        let wasConfident = false;
        for (const mappings of this.mappings.values()) {
            for (let i = 0; i < mappings.length; i++) {
                const mapping = mappings[i];
                if (mapping.externalIp === host && mapping.externalPort === port && mapping.protocol === protocol) {
                    this.log('removing verification of %s:%s to %s:%s %s IP mapping', mapping.externalIp, mapping.externalPort, host, port, protocol);
                    wasConfident = wasConfident || mapping.verified;
                    mapping.verified = false;
                    mapping.expires = Date.now() + ttl;
                }
            }
        }
        return wasConfident;
    }
}
//# sourceMappingURL=ip-mappings.js.map