# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
#
# SPDX-License-Identifier: MPL-2.0
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0.  If a copy of the MPL was not distributed with this
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
#
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.

from datetime import timedelta

import os

import dns.rcode
import dns.rdatatype

from isctest.vars.algorithms import RSASHA256
from nsec3.common import NSEC3_MARK, check_auth_nsec3, check_nsec3param

import isctest

pytestmark = NSEC3_MARK

DNSKEY_TTL = int(timedelta(hours=1).total_seconds())
ZSK_LIFETIME = int(timedelta(days=90).total_seconds())

# include the following zones when rendering named configs
ZONES = {
    "retransfer.kasp",
}


def bootstrap():
    return {
        "zones": ZONES,
    }


def perform_nsec3_tests(server, params):
    # Get test parameters.
    zone = params["zone"]
    fqdn = f"{zone}."
    policy = params["policy"]
    keydir = server.identifier
    minimum = params.get("soa-minimum", 3600)
    expected = isctest.kasp.policy_to_properties(
        ttl=DNSKEY_TTL, keys=params["key-properties"]
    )

    iterations = 0
    optout = 0
    saltlen = 0

    match = f"{fqdn} {minimum} IN NSEC3PARAM 1 0 {iterations}"

    # Test case.
    isctest.log.info(f"check nsec3 case zone {zone} policy {policy}")

    # First make sure the zone is properly signed.
    isctest.kasp.wait_keymgr_done(server, zone)

    keys = isctest.kasp.keydir_to_keylist(zone, keydir)
    ksks = [k for k in keys if k.is_ksk()]
    zsks = [k for k in keys if k.is_zsk()]
    isctest.kasp.check_keys(zone, keys, expected)
    isctest.kasp.check_dnssec_verify(server, zone)
    isctest.kasp.check_apex(server, zone, ksks, zsks)

    query = isctest.query.create(fqdn, dns.rdatatype.NSEC3PARAM)
    response = isctest.query.tcp(query, server.ip, server.ports.dns, timeout=3)
    assert response.rcode() == dns.rcode.NOERROR

    salt = check_nsec3param(response, match, saltlen)

    query = isctest.query.create(f"nosuchname.{fqdn}", dns.rdatatype.A)
    response = isctest.query.tcp(query, server.ip, server.ports.dns, timeout=3)
    assert response.rcode() == dns.rcode.NXDOMAIN
    check_auth_nsec3(response, iterations, optout, salt)

    return salt


def test_nsec3_retransfer(servers, templates):
    ns2 = servers["ns2"]
    ns3 = servers["ns3"]

    params = {
        "zone": "retransfer.kasp",
        "policy": "nsec3rsa256",
        "key-properties": [
            f"ksk 0 {RSASHA256.number} 2048 goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden",
            f"zsk {ZSK_LIFETIME} {RSASHA256.number} 2048 goal:omnipresent dnskey:rumoured zrrsig:rumoured",
        ],
    }

    zone = params["zone"]
    salt = perform_nsec3_tests(ns3, params)

    # Stop primary.
    ns2.stop()

    # Update the zone.
    serial = 10
    templates.render(f"{ns2.identifier}/{zone}.db", {"serial": serial})

    with ns2.watch_log_from_here() as watcher:
        ns2.start(["--noclean", "--restart", "--port", os.environ["PORT"]])
        watcher.wait_for_line("all zones loaded")

    # Test NSEC3 and NSEC3PARAM is the same after retransfer.
    isctest.log.info(f"check zone {zone} after retransfer has salt {salt}")
    prevsalt = salt

    # Retransfer zone, NSEC3 should stay the same.
    with ns3.watch_log_from_here() as watcher:
        ns3.rndc(f"retransfer {zone}")
        # When sending notifies, the zone should be up to date.
        watcher.wait_for_line(f"zone_needdump: zone {zone}/IN (signed): enter")

    salt = perform_nsec3_tests(ns3, params)
    assert prevsalt == salt
