"""Pruebas de rov.c contra los casos del RFC 6811, y del cache RTR contra el RFC 8210.

Ninguna necesita una topología.
"""
import os
import socket
import struct
import subprocess
import sys
import time
import unittest

from netlab import HERE

EXAMPLE = os.path.join(HERE, "vrps-example.txt")


def rov(prefix, asn, vrps=EXAMPLE):
    done = subprocess.run([os.path.join(HERE, "rov"), vrps, prefix, str(asn)],
                          capture_output=True, text=True)
    return done.returncode, done.stdout.strip().splitlines()[-1] if done.stdout else ""


class Classification(unittest.TestCase):
    def test_guide_cases(self):
        # Un ROA autoriza al AS 64600 a originar 198.51.100.0/24 con largo máximo 24.
        for prefix, asn, state in (("198.51.100.0/24", 64600, "valid"),
                                   ("198.51.100.0/24", 64666, "invalid"),
                                   ("198.51.100.0/25", 64600, "invalid"),
                                   ("198.51.100.0/25", 64666, "invalid"),
                                   ("198.51.0.0/16", 64600, "not found"),
                                   ("198.51.101.0/24", 64600, "not found")):
            with self.subTest(prefix=prefix, asn=asn):
                self.assertEqual(rov(prefix, asn), (0, state))

    def test_max_length_allows_more_specifics_up_to_it(self):
        self.assertEqual(rov("203.0.113.128/25", 64601), (0, "valid"))
        self.assertEqual(rov("203.0.113.128/26", 64601), (0, "invalid"))

    def test_as_zero_never_matches(self):
        self.assertEqual(rov("192.0.2.0/24", 0), (0, "invalid"))

    def test_one_matching_vrp_is_enough(self):
        self.assertEqual(rov("10.0.0.0/8", 64701), (0, "valid"))
        self.assertEqual(rov("10.1.0.0/16", 64700), (0, "valid"))
        # La segunda VRP cubre el /16, pero no lo autoriza: basta con la primera.
        self.assertEqual(rov("10.1.0.0/16", 64701), (0, "invalid"))

    def test_bad_arguments_are_rejected(self):
        for prefix, asn in (("10.0.0.0", 1), ("10.0.0.0/33", 1), ("x/8", 1), ("10.0.0.0/8", "y")):
            with self.subTest(prefix=prefix, asn=asn):
                self.assertEqual(rov(prefix, asn)[0], 2)


class RtrCache(unittest.TestCase):
    """Un router que habla RTR a mano: mismos bytes que manda FRRouting."""

    def setUp(self):
        with socket.socket() as probe:
            probe.bind(("127.0.0.1", 0))
            self.port = probe.getsockname()[1]
        self.cache = subprocess.Popen(
            [sys.executable, os.path.join(HERE, "bgp", "cache", "rtr_cache.py"),
             os.path.join(HERE, "bgp", "cache", "vrps.txt"), "--port", str(self.port)],
            stdout=subprocess.DEVNULL)
        self.addCleanup(self.cache.wait)
        self.addCleanup(self.cache.kill)
        deadline = time.monotonic() + 5
        while True:
            try:
                self.conn = socket.create_connection(("127.0.0.1", self.port), timeout=3)
                break
            except OSError:
                if time.monotonic() > deadline:
                    raise
                time.sleep(0.05)
        self.addCleanup(self.conn.close)

    def read_pdu(self):
        data = b""
        while len(data) < 8:
            data += self.conn.recv(8 - len(data))
        version, kind, field, length = struct.unpack("!BBHI", data)
        while len(data) < length:
            data += self.conn.recv(length - len(data))
        return version, kind, field, data

    def test_reset_query_gets_the_whole_table(self):
        self.conn.sendall(struct.pack("!BBHI", 1, 2, 0, 8))
        version, kind, session, _ = self.read_pdu()
        self.assertEqual((version, kind), (1, 3))
        prefixes = []
        while True:
            _, kind, field, data = self.read_pdu()
            if kind == 7:
                break
            self.assertEqual((kind, len(data)), (4, 20))
            _, length, max_length, _, address, asn = struct.unpack("!BBBB4sI", data[8:])
            prefixes.append((socket.inet_ntoa(address), length, max_length, asn))
        self.assertEqual(field, session)
        self.assertEqual(len(data), 24)
        self.assertIn(("100.64.3.0", 24, 24, 64603), prefixes)
        self.assertIn(("100.64.1.0", 24, 24, 64601), prefixes)

    def test_version_zero_is_answered_in_version_zero(self):
        self.conn.sendall(struct.pack("!BBHI", 0, 2, 0, 8))
        version = kind = None
        while kind != 7:
            version, kind, _, data = self.read_pdu()
            self.assertEqual(version, 0)
        self.assertEqual(len(data), 12)

    def test_unknown_version_gets_an_error_report(self):
        self.conn.sendall(struct.pack("!BBHI", 9, 2, 0, 8))
        _, kind, code, _ = self.read_pdu()
        self.assertEqual((kind, code), (10, 4))


if __name__ == "__main__":
    unittest.main()
