diff --git a/pytcp/lib/net_addr/__init__.py b/pytcp/lib/net_addr/__init__.py index 7718af74..cede8283 100644 --- a/pytcp/lib/net_addr/__init__.py +++ b/pytcp/lib/net_addr/__init__.py @@ -37,11 +37,13 @@ Ip4AddressFormatError, Ip4HostFormatError, Ip4HostGatewayError, + Ip4HostSanityError, Ip4MaskFormatError, Ip4NetworkFormatError, Ip6AddressFormatError, Ip6HostFormatError, Ip6HostGatewayError, + Ip6HostSanityError, Ip6MaskFormatError, Ip6NetworkFormatError, IpAddressFormatError, @@ -52,11 +54,11 @@ MacAddressFormatError, ) from .ip4_address import IP4__ADDRESS_LEN, Ip4Address -from .ip4_host import Ip4Host +from .ip4_host import Ip4Host, Ip4HostOrigin from .ip4_mask import Ip4Mask from .ip4_network import Ip4Network from .ip6_address import IP6__ADDRESS_LEN, Ip6Address -from .ip6_host import Ip6Host +from .ip6_host import Ip6Host, Ip6HostOrigin from .ip6_mask import Ip6Mask from .ip6_network import Ip6Network from .ip_address import IpAddress diff --git a/pytcp/lib/net_addr/errors.py b/pytcp/lib/net_addr/errors.py index 5768847a..ed803f49 100644 --- a/pytcp/lib/net_addr/errors.py +++ b/pytcp/lib/net_addr/errors.py @@ -173,7 +173,7 @@ class Ip6HostSanityError(IpHostFormatError): def __init__(self, message: Any, /): super().__init__( - f"The IPv6 host doesn't belong to provided network: {message!r}" + f"The IPv6 address doesn't belong to the provided network: {message!r}" ) diff --git a/pytcp/lib/net_addr/ip6_host.py b/pytcp/lib/net_addr/ip6_host.py index 8cccc611..b58a35a3 100755 --- a/pytcp/lib/net_addr/ip6_host.py +++ b/pytcp/lib/net_addr/ip6_host.py @@ -60,7 +60,7 @@ class Ip6HostOrigin(IpHostOrigin): """ STATIC = auto() - ND = auto() + AUTOCONFIG = auto() DHCP = auto() UNKNOWN = auto() @@ -97,7 +97,7 @@ def __init__( self._origin = origin or Ip6HostOrigin.UNKNOWN self._expiration_time = expiration_time or 0 - if self._origin in {Ip6HostOrigin.ND, Ip6HostOrigin.DHCP}: + if self._origin in {Ip6HostOrigin.AUTOCONFIG, Ip6HostOrigin.DHCP}: assert self._expiration_time >= int(time.time()) else: assert self._expiration_time == 0 @@ -155,7 +155,8 @@ def _validate_gateway(self, address: Ip6Address | None, /) -> None: """ if address is not None and ( - address not in Ip6Network("fe80::/10") + not address.is_global + and not address.is_link_local or address == self._network.address or address == self._address ): diff --git a/tests/unit/lib/net_addr/test__ip4_host.py b/tests/unit/lib/net_addr/test__ip4_host.py index ac0e720c..b46a9303 100644 --- a/tests/unit/lib/net_addr/test__ip4_host.py +++ b/tests/unit/lib/net_addr/test__ip4_host.py @@ -39,11 +39,14 @@ from parameterized import parameterized_class # type: ignore from testslide import TestCase -from pytcp.lib.net_addr.errors import Ip4HostSanityError -from pytcp.lib.net_addr.ip4_address import Ip4Address -from pytcp.lib.net_addr.ip4_host import Ip4Host, Ip4HostOrigin -from pytcp.lib.net_addr.ip4_mask import Ip4Mask -from pytcp.lib.net_addr.ip4_network import Ip4Network +from pytcp.lib.net_addr import ( + Ip4Address, + Ip4Host, + Ip4HostOrigin, + Ip4HostSanityError, + Ip4Mask, + Ip4Network, +) IP4_ADDRESS_EXPIRATION_TIME = int(time.time() + 3600) @@ -96,7 +99,6 @@ "_kwargs": { "gateway": Ip4Address("192.168.1.1"), "origin": Ip4HostOrigin.STATIC, - "expiration_time": 0, }, "_results": { "__str__": "192.168.1.100/24", @@ -120,7 +122,6 @@ "_kwargs": { "gateway": Ip4Address("192.168.1.1"), "origin": Ip4HostOrigin.STATIC, - "expiration_time": 0, }, "_results": { "__str__": "192.168.1.100/24", @@ -267,7 +268,7 @@ def test__net_addr__ip4_host__expiration_time(self) -> None: @parameterized_class( [ { - "_description": "Test the IPv4 host: 192.168.1.100/24 (Ip4Address, Ip4Network)", + "_description": "Test the IPv4 host where address is not part of the network.", "_args": [ (Ip4Address("192.168.1.100"), Ip4Network("192.168.2.0/24")) ], diff --git a/tests/unit/lib/net_addr/test__ip6_host.py b/tests/unit/lib/net_addr/test__ip6_host.py new file mode 100644 index 00000000..a76c23ca --- /dev/null +++ b/tests/unit/lib/net_addr/test__ip6_host.py @@ -0,0 +1,311 @@ +#!/usr/bin/env python3 + +################################################################################ +## ## +## PyTCP - Python TCP/IP stack ## +## Copyright (C) 2020-present Sebastian Majewski ## +## ## +## This program is free software: you can redistribute it and/or modify ## +## it under the terms of the GNU General Public License as published by ## +## the Free Software Foundation, either version 3 of the License, or ## +## (at your option) any later version. ## +## ## +## This program is distributed in the hope that it will be useful, ## +## but WITHOUT ANY WARRANTY; without even the implied warranty of ## +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## +## GNU General Public License for more details. ## +## ## +## You should have received a copy of the GNU General Public License ## +## along with this program. If not, see . ## +## ## +## Author's email: ccie18643@gmail.com ## +## Github repository: https://github.com/ccie18643/PyTCP ## +## ## +################################################################################ + + +""" +This module contains tests for the NetAddr package IPv6 host support class. + +tests/unit/lib/net_addr/test__ip6_host.py + +ver 3.0.2 +""" + + +import time +from typing import Any + +from parameterized import parameterized_class # type: ignore +from testslide import TestCase + +from pytcp.lib.net_addr import ( + Ip6Address, + Ip6Host, + Ip6HostOrigin, + Ip6HostSanityError, + Ip6Mask, + Ip6Network, +) + +IP6_ADDRESS_EXPIRATION_TIME = int(time.time() + 3600) + + +@parameterized_class( + [ + { + "_description": "Test the IPv6 host: 2001:b:c:d:1:2:3:4/64 (str)", + "_args": ["2001:b:c:d:1:2:3:4/64"], + "_kwargs": { + "gateway": Ip6Address("2001:b:c:d::1"), + "origin": Ip6HostOrigin.AUTOCONFIG, + "expiration_time": IP6_ADDRESS_EXPIRATION_TIME, + }, + "_results": { + "__str__": "2001:b:c:d:1:2:3:4/64", + "__repr__": "Ip6Host('2001:b:c:d:1:2:3:4/64')", + "__hash__": hash("Ip6Host('2001:b:c:d:1:2:3:4/64')"), + "version": 6, + "is_ip6": True, + "is_ip4": False, + "address": Ip6Address("2001:b:c:d:1:2:3:4"), + "network": Ip6Network("2001:b:c:d:1::/64"), + "gateway": Ip6Address("2001:b:c:d::1"), + "origin": Ip6HostOrigin.AUTOCONFIG, + "expiration_time": IP6_ADDRESS_EXPIRATION_TIME, + }, + }, + { + "_description": "Test the IPv6 host: 2001:b:c:d:1:2:3:4/64 (Ip6Host)", + "_args": [Ip6Host("2001:b:c:d:1:2:3:4/64")], + "_kwargs": {}, + "_results": { + "__str__": "2001:b:c:d:1:2:3:4/64", + "__repr__": "Ip6Host('2001:b:c:d:1:2:3:4/64')", + "__hash__": hash("Ip6Host('2001:b:c:d:1:2:3:4/64')"), + "version": 6, + "is_ip6": True, + "is_ip4": False, + "address": Ip6Address("2001:b:c:d:1:2:3:4"), + "network": Ip6Network("2001:b:c:d:1::/64"), + "gateway": None, + "origin": Ip6HostOrigin.UNKNOWN, + "expiration_time": 0, + }, + }, + { + "_description": "Test the IPv6 host: 2001:b:c:d:1:2:3:4/64 (Ip6Address, Ip6Mask)", + "_args": [(Ip6Address("2001:b:c:d:1:2:3:4"), Ip6Mask("/64"))], + "_kwargs": { + "gateway": Ip6Address("2001:b:c:d::1"), + "origin": Ip6HostOrigin.DHCP, + "expiration_time": IP6_ADDRESS_EXPIRATION_TIME, + }, + "_results": { + "__str__": "2001:b:c:d:1:2:3:4/64", + "__repr__": "Ip6Host('2001:b:c:d:1:2:3:4/64')", + "__hash__": hash("Ip6Host('2001:b:c:d:1:2:3:4/64')"), + "version": 6, + "is_ip6": True, + "is_ip4": False, + "address": Ip6Address("2001:b:c:d:1:2:3:4"), + "network": Ip6Network("2001:b:c:d:1::/64"), + "gateway": Ip6Address("2001:b:c:d::1"), + "origin": Ip6HostOrigin.DHCP, + "expiration_time": IP6_ADDRESS_EXPIRATION_TIME, + }, + }, + { + "_description": "Test the IPv6 host: 2001:b:c:d:1:2:3:4/64 (Ip6Address, Ip6Network)", + "_args": [ + ( + Ip6Address("2001:b:c:d:1:2:3:4"), + Ip6Network("2001:b:c:d::/64"), + ) + ], + "_kwargs": { + "gateway": Ip6Address("2001:b:c:d::1"), + "origin": Ip6HostOrigin.STATIC, + }, + "_results": { + "__str__": "2001:b:c:d:1:2:3:4/64", + "__repr__": "Ip6Host('2001:b:c:d:1:2:3:4/64')", + "__hash__": hash("Ip6Host('2001:b:c:d:1:2:3:4/64')"), + "version": 6, + "is_ip6": True, + "is_ip4": False, + "address": Ip6Address("2001:b:c:d:1:2:3:4"), + "network": Ip6Network("2001:b:c:d:1::/64"), + "gateway": Ip6Address("2001:b:c:d::1"), + "origin": Ip6HostOrigin.STATIC, + "expiration_time": 0, + }, + }, + ] +) +class TestNetAddrIp6Host(TestCase): + """ + The NetAddr IPv6 Host tests. + """ + + _description: str + _args: dict[str, Any] + _kwargs: dict[str, Any] + _results: dict[str, Any] + + def setUp(self) -> None: + """ + Initialize the IPv6 host object with testcase arguments. + """ + + self._ip6_host = Ip6Host(*self._args, **self._kwargs) + + def test__net_addr__ip6_host__str(self) -> None: + """ + Ensure the IPv6 host '__str__()' method returns a correct value. + """ + + self.assertEqual( + str(self._ip6_host), + self._results["__str__"], + ) + + def test__net_addr__ip6_host__repr(self) -> None: + """ + Ensure the IPv6 host '__repr__()' method returns a correct value. + """ + + self.assertEqual( + repr(self._ip6_host), + self._results["__repr__"], + ) + + def test__net_addr__ip6_host__eq(self) -> None: + """ + Ensure the IPv6 host '__eq__()' method returns a correct value. + """ + + self.assertTrue( + self._ip6_host == self._ip6_host, + ) + + self.assertFalse( + self._ip6_host == "not an IPv6 host", + ) + + def test__net_addr__ip6_host__hash(self) -> None: + """ + Ensure the IPv6 host '__hash__()' method returns a correct value. + """ + + self.assertEqual( + hash(self._ip6_host), + self._results["__hash__"], + ) + + def test__net_addr__ip6_host__version(self) -> None: + """ + Ensure the IPv6 host 'version' property returns a correct value. + """ + + self.assertEqual( + self._ip6_host.version, + self._results["version"], + ) + + def test__net_addr__ip6_host__is_ip4(self) -> None: + """ + Ensure the IPv6 host 'is_ip4' property returns a correct + value. + """ + + self.assertEqual( + self._ip6_host.is_ip4, + self._results["is_ip4"], + ) + + def test__net_addr__ip6_host__is_ip6(self) -> None: + """ + Ensure the IPv6 host 'is_ip6' property returns a correct + value. + """ + + self.assertEqual( + self._ip6_host.is_ip6, + self._results["is_ip6"], + ) + + def test__net_addr__ip6_host__gateway(self) -> None: + """ + Ensure the IPv6 host 'gateway' property returns a correct + value. + """ + + self.assertEqual( + self._ip6_host.gateway, + self._results["gateway"], + ) + + def test__net_addr__ip6_host__origin(self) -> None: + """ + Ensure the IPv6 host 'origin' property returns a correct + value. + """ + + self.assertEqual( + self._ip6_host.origin, + self._results["origin"], + ) + + def test__net_addr__ip6_host__expiration_time(self) -> None: + """ + Ensure the IPv6 host 'expiration_time' property returns a correct + value. + """ + + self.assertEqual( + self._ip6_host.expiration_time, + self._results["expiration_time"], + ) + + +@parameterized_class( + [ + { + "_description": "Test the IPv6 host where address is not part of the network.", + "_args": [ + (Ip6Address("a::1:2:3:4"), Ip6Network("b::/64")), + ], + "_kwargs": {}, + "_results": { + "error": Ip6HostSanityError, + "error_message": ( + "The IPv6 address doesn't belong to the provided network: " + "(Ip6Address('a::1:2:3:4'), Ip6Network('b::/64'))" + ), + }, + }, + ] +) +class TestNetAddrIp6NetworkErrors(TestCase): + """ + The NetAddr IPv6 host error tests. + """ + + _description: str + _args: dict[str, Any] + _kwargs: dict[str, Any] + _results: dict[str, Any] + + def test__net_addr__ip6_host__errors(self) -> None: + """ + Ensure the IPv6 host raises an error on invalid input. + """ + + with self.assertRaises(self._results["error"]) as error: + Ip6Host(*self._args, **self._kwargs) + + self.assertEqual( + str(error.exception), + self._results["error_message"], + ) diff --git a/tests__legacy/unit/test__lib__ip6__address.py b/tests__legacy/unit/test__lib__ip6__address.py index 538ab1be..77a08491 100755 --- a/tests__legacy/unit/test__lib__ip6__address.py +++ b/tests__legacy/unit/test__lib__ip6__address.py @@ -758,13 +758,13 @@ def test__gateway_setter__success__none(self) -> None: self.assertIsNone(host._gateway) - def test__gateway_setter__error__not_lla(self) -> None: + def test__gateway_setter__error__not_lla_gua(self) -> None: """ Ensure that the 'gateway' property setter raises the 'Ip4HostGatewayError' - exception when the provided gateway address is not LLA. + exception when the provided gateway address is not LLA or GUA. """ - gateway = Ip6Address("2001::1") + gateway = Ip6Address("::") host = Ip6Host("2001::7/64") with self.assertRaises(Ip6HostGatewayError) as error: