#!/usr/bin/python3

# 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.

# pylint: disable=unused-variable

import socket
import time

import pytest

TIMEOUT = 10


def create_msg(qname, qtype):
    import dns.message
    msg = dns.message.make_query(qname, qtype, want_dnssec=True,
                                 use_edns=0, payload=4096)
    return msg


def timeout():
    return time.time() + TIMEOUT


@pytest.mark.dnspython
@pytest.mark.dnspython2
def test_initial_timeout(port):
    #
    # The initial timeout is 2.5 seconds, so this should timeout
    #
    import dns.query

    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.connect(("10.53.0.1", port))

        time.sleep(3)

        msg = create_msg("example.", "A")

        with pytest.raises(EOFError):
            try:
                (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
                (response, rtime) = dns.query.receive_tcp(sock, timeout())
            except ConnectionResetError as e:
                raise EOFError from e


@pytest.mark.dnspython
@pytest.mark.dnspython2
def test_idle_timeout(port):
    #
    # The idle timeout is 5 seconds, so the third message should fail
    #
    import dns.rcode

    msg = create_msg("example.", "A")
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.connect(("10.53.0.1", port))

        time.sleep(1)

        (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
        (response, rtime) = dns.query.receive_tcp(sock, timeout())

        time.sleep(2)

        (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
        (response, rtime) = dns.query.receive_tcp(sock, timeout())

        time.sleep(6)

        with pytest.raises(EOFError):
            try:
                (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
                (response, rtime) = dns.query.receive_tcp(sock, timeout())
            except ConnectionResetError as e:
                raise EOFError from e


@pytest.mark.dnspython
@pytest.mark.dnspython2
def test_keepalive_timeout(port):
    #
    # Keepalive is 7 seconds, so the third message should succeed.
    #
    import dns.rcode

    msg = create_msg("example.", "A")
    kopt = dns.edns.GenericOption(11, b'\x00')
    msg.use_edns(edns=True, payload=4096, options=[kopt])

    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.connect(("10.53.0.1", port))

        time.sleep(1)

        (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
        (response, rtime) = dns.query.receive_tcp(sock, timeout())

        time.sleep(2)

        (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
        (response, rtime) = dns.query.receive_tcp(sock, timeout())

        time.sleep(6)

        (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
        (response, rtime) = dns.query.receive_tcp(sock, timeout())


@pytest.mark.dnspython
@pytest.mark.dnspython2
def test_pipelining_timeout(port):
    #
    # The pipelining should only timeout after the last message is received
    #
    import dns.query

    msg = create_msg("example.", "A")
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.connect(("10.53.0.1", port))

        time.sleep(1)

        # Send and receive 25 DNS queries
        for n in range(25):
            (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
        for n in range(25):
            (response, rtime) = dns.query.receive_tcp(sock, timeout())

        time.sleep(3)

        # Send and receive 25 DNS queries
        for n in range(25):
            (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
        for n in range(25):
            (response, rtime) = dns.query.receive_tcp(sock, timeout())

        time.sleep(6)

        with pytest.raises(EOFError):
            try:
                (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
                (response, rtime) = dns.query.receive_tcp(sock, timeout())
            except ConnectionResetError as e:
                raise EOFError from e


@pytest.mark.dnspython
@pytest.mark.dnspython2
def test_long_axfr(port):
    #
    # The timers should not fire during AXFR, thus the connection should not
    # close abruptly
    #
    import dns.query
    import dns.rdataclass
    import dns.rdatatype

    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.connect(("10.53.0.1", port))

        name = dns.name.from_text("example.")
        msg = create_msg("example.", "AXFR")
        (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())

        # Receive the initial DNS message with SOA
        (response, rtime) = dns.query.receive_tcp(sock, timeout(),
                                                  one_rr_per_rrset=True)
        soa = response.get_rrset(dns.message.ANSWER, name,
                                 dns.rdataclass.IN, dns.rdatatype.SOA)
        assert soa is not None

        # Pull DNS message from wire until the second SOA is received
        while True:
            (response, rtime) = dns.query.receive_tcp(sock, timeout(),
                                                      one_rr_per_rrset=True)
            soa = response.get_rrset(dns.message.ANSWER, name,
                                     dns.rdataclass.IN, dns.rdatatype.SOA)
            if soa is not None:
                break
        assert soa is not None
