301 lines
11 KiB
Python
301 lines
11 KiB
Python
"""
|
|
Clase híbrida para direcciones IPv4
|
|
"""
|
|
from class_base import ClassBase
|
|
from sympy_Base import SympyClassBase
|
|
from typing import Optional, Union
|
|
import re
|
|
import sympy
|
|
|
|
class IP4Mask(ClassBase):
|
|
"""
|
|
Helper class to manage IPv4 masks.
|
|
It can be initialized with an integer prefix (0-32) or a netmask string (e.g., "255.255.255.0").
|
|
"""
|
|
_prefix: int
|
|
_mask_int: int
|
|
|
|
def __init__(self, mask_input: Union[int, str]):
|
|
prefix = self._parse_mask(mask_input)
|
|
super().__init__(prefix, str(mask_input))
|
|
self._prefix = prefix
|
|
self._mask_int = self._prefix_to_mask_int(self._prefix)
|
|
|
|
def _parse_mask(self, mask_input: Union[int, str]) -> int:
|
|
"""Helper to parse mask_input and return prefix."""
|
|
prefix_val: int
|
|
if isinstance(mask_input, int):
|
|
if not (0 <= mask_input <= 32):
|
|
raise ValueError(f"Invalid prefix length: {mask_input}. Must be between 0 and 32.")
|
|
prefix_val = mask_input
|
|
elif isinstance(mask_input, str):
|
|
try:
|
|
parsed_int = int(mask_input)
|
|
if not (0 <= parsed_int <= 32):
|
|
raise ValueError(f"Invalid prefix string: '{mask_input}'. Must be between 0 and 32.")
|
|
prefix_val = parsed_int
|
|
except ValueError:
|
|
parsed_prefix_from_str = self._netmask_str_to_prefix(mask_input)
|
|
if parsed_prefix_from_str is None:
|
|
raise ValueError(f"Invalid netmask string format: '{mask_input}'.")
|
|
prefix_val = parsed_prefix_from_str
|
|
else:
|
|
raise TypeError(f"Invalid type for mask_input: {type(mask_input)}. Must be int or str.")
|
|
return prefix_val
|
|
|
|
@staticmethod
|
|
def _is_valid_ip_octet_str(octet_str: str) -> bool:
|
|
try:
|
|
octet_val = int(octet_str)
|
|
return 0 <= octet_val <= 255
|
|
except ValueError:
|
|
return False
|
|
|
|
@staticmethod
|
|
def _is_valid_ip_string(ip_str: str) -> bool:
|
|
parts = ip_str.split('.')
|
|
if len(parts) != 4:
|
|
return False
|
|
return all(IP4Mask._is_valid_ip_octet_str(p.strip()) for p in parts)
|
|
|
|
@staticmethod
|
|
def _netmask_str_to_prefix(netmask_str: str) -> Optional[int]:
|
|
if not IP4Mask._is_valid_ip_string(netmask_str):
|
|
return None
|
|
|
|
octets = [int(x) for x in netmask_str.split('.')]
|
|
mask_val = (octets[0] << 24) | (octets[1] << 16) | (octets[2] << 8) | octets[3]
|
|
|
|
binary_mask = bin(mask_val)[2:].zfill(32)
|
|
if not re.fullmatch(r"1*0*", binary_mask): # Must be contiguous 1s followed by 0s
|
|
return None
|
|
|
|
return binary_mask.count('1')
|
|
|
|
@staticmethod
|
|
def _prefix_to_mask_int(prefix: int) -> int:
|
|
if not (0 <= prefix <= 32): # Should be validated before calling
|
|
raise ValueError("Prefix out of range")
|
|
if prefix == 0:
|
|
return 0
|
|
return (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF
|
|
|
|
def get_mask_str(self) -> str:
|
|
return f"{(self._mask_int >> 24) & 0xFF}.{(self._mask_int >> 16) & 0xFF}.{(self._mask_int >> 8) & 0xFF}.{self._mask_int & 0xFF}"
|
|
|
|
def get_prefix_int(self) -> int:
|
|
return self._prefix
|
|
|
|
def __str__(self) -> str:
|
|
return self.get_mask_str()
|
|
|
|
def __repr__(self) -> str:
|
|
return f"IP4Mask({self._prefix})"
|
|
|
|
def __eq__(self, other):
|
|
if isinstance(other, IP4Mask):
|
|
return self._prefix == other._prefix
|
|
return False
|
|
|
|
@staticmethod
|
|
def Helper(input_str):
|
|
if re.match(r"^\s*IP4Mask\\b", input_str, re.IGNORECASE):
|
|
return 'Máscara: IP4Mask[24], IP4Mask[255.255.255.0]. Funciones: get_prefix_int(), get_netmask_str(), hosts_count()'
|
|
return None
|
|
|
|
@staticmethod
|
|
def PopupFunctionList():
|
|
return [
|
|
("get_prefix_int", "Obtiene prefijo CIDR (ej: 24)"),
|
|
("get_netmask_str", "Obtiene máscara como string (ej: 255.255.255.0)"),
|
|
("hosts_count", "Número de hosts disponibles"),
|
|
("to_sympy", "Convierte a expresión SymPy para álgebra"),
|
|
]
|
|
|
|
def hosts_count(self):
|
|
"""Número de hosts disponibles"""
|
|
return 2**(32 - self._prefix) - 2 if self._prefix < 31 else 0
|
|
|
|
def to_sympy(self):
|
|
"""Convierte a SymPy cuando se necesite álgebra"""
|
|
return sympy.sympify(self._prefix)
|
|
|
|
class Class_IP4(SympyClassBase):
|
|
"""Clase híbrida para direcciones IPv4"""
|
|
|
|
def __new__(cls, *args, **kwargs):
|
|
temp_ip_str_candidate = args[0] if args else ""
|
|
temp_ip_str = ""
|
|
if isinstance(temp_ip_str_candidate, str):
|
|
if '/' in temp_ip_str_candidate:
|
|
temp_ip_str = temp_ip_str_candidate.split('/', 1)[0].strip()
|
|
elif ' ' in temp_ip_str_candidate:
|
|
temp_ip_str = temp_ip_str_candidate.split(' ', 1)[0].strip()
|
|
else:
|
|
temp_ip_str = temp_ip_str_candidate.strip()
|
|
|
|
if IP4Mask._is_valid_ip_string(temp_ip_str):
|
|
ip_parts_for_new = [int(x) for x in temp_ip_str.split('.')]
|
|
ip_int_for_new = (ip_parts_for_new[0] << 24) | \
|
|
(ip_parts_for_new[1] << 16) | \
|
|
(ip_parts_for_new[2] << 8) | \
|
|
ip_parts_for_new[3]
|
|
obj = SympyClassBase.__new__(cls, ip_int_for_new)
|
|
else:
|
|
raise ValueError(f"Invalid IP address format for __new__: {temp_ip_str}")
|
|
else:
|
|
if not args:
|
|
raise ValueError("IP4 constructor requires arguments.")
|
|
obj = SympyClassBase.__new__(cls, *args)
|
|
|
|
return obj
|
|
|
|
def __init__(self, *args):
|
|
self._raw_constructor_args = args
|
|
|
|
ip_str: str
|
|
self._mask_obj: Optional[IP4Mask] = None
|
|
|
|
if not args:
|
|
raise ValueError("IP4 constructor requires at least one argument (the IP string).")
|
|
|
|
ip_str_candidate = args[0]
|
|
if not isinstance(ip_str_candidate, str):
|
|
raise TypeError(f"First argument to IP4 must be a string (IP address), got {type(ip_str_candidate)}")
|
|
|
|
if len(args) == 1:
|
|
input_str = args[0]
|
|
if '/' in input_str:
|
|
parts = input_str.split('/', 1)
|
|
ip_str = parts[0].strip()
|
|
if len(parts) > 1 and parts[1].strip():
|
|
self._mask_obj = IP4Mask(parts[1].strip())
|
|
elif ' ' in input_str:
|
|
parts = input_str.split(' ', 1)
|
|
ip_str = parts[0].strip()
|
|
if len(parts) > 1 and parts[1].strip():
|
|
self._mask_obj = IP4Mask(parts[1].strip())
|
|
else:
|
|
ip_str = input_str.strip()
|
|
|
|
elif len(args) == 2:
|
|
ip_str = args[0].strip()
|
|
mask_arg = args[1]
|
|
|
|
if isinstance(mask_arg, IP4Mask):
|
|
self._mask_obj = mask_arg
|
|
elif isinstance(mask_arg, (str, int)):
|
|
self._mask_obj = IP4Mask(mask_arg)
|
|
else:
|
|
raise TypeError(f"Second argument (mask) for IP4 must be int, str, or IP4Mask instance, got {type(mask_arg)}")
|
|
else:
|
|
raise ValueError(f"IP4 constructor takes 1 or 2 arguments, got {len(args)}: {args}")
|
|
|
|
if not IP4Mask._is_valid_ip_string(ip_str):
|
|
raise ValueError(f"Invalid IP address format: {ip_str}")
|
|
|
|
self._ip_str = ip_str
|
|
ip_parts = [int(x) for x in ip_str.split('.')]
|
|
self._ip_int = (ip_parts[0] << 24) | (ip_parts[1] << 16) | (ip_parts[2] << 8) | ip_parts[3]
|
|
|
|
sympy_base_original_str: str
|
|
if len(args) == 1 and isinstance(args[0], str):
|
|
sympy_base_original_str = args[0]
|
|
elif len(args) == 2:
|
|
mask_arg_for_repr = args[1]
|
|
if isinstance(mask_arg_for_repr, IP4Mask):
|
|
mask_repr_str = str(mask_arg_for_repr.get_prefix_int())
|
|
elif isinstance(mask_arg_for_repr, int):
|
|
mask_repr_str = str(mask_arg_for_repr)
|
|
else:
|
|
mask_repr_str = str(mask_arg_for_repr)
|
|
sympy_base_original_str = f"{args[0]};{mask_repr_str}"
|
|
else:
|
|
sympy_base_original_str = self._ip_str
|
|
|
|
super().__init__(self._ip_int, sympy_base_original_str)
|
|
|
|
def __repr__(self):
|
|
arg_reprs = [repr(arg) for arg in self._raw_constructor_args]
|
|
return f"{self.__class__.__name__}({', '.join(arg_reprs)})"
|
|
|
|
def __str__(self):
|
|
if self._mask_obj:
|
|
return f"{self._ip_str}/{self._mask_obj.get_prefix_int()}"
|
|
return self._ip_str
|
|
|
|
def _sympystr(self, printer):
|
|
return str(self)
|
|
|
|
@staticmethod
|
|
def Helper(input_str):
|
|
if re.match(r"^\s*IP4\\b", input_str, re.IGNORECASE):
|
|
return ('Ej: IP4[192.168.1.1/24], IP4[10.0.0.1;8], IP4[172.16.0.5;255.255.0.0]\\n'
|
|
'Funciones: NetworkAddress(), BroadcastAddress(), Nodes(), get_netmask_str(), get_prefix_length(), mask()')
|
|
return None
|
|
|
|
@staticmethod
|
|
def PopupFunctionList():
|
|
return [
|
|
("NetworkAddress", "Dirección de red"),
|
|
("BroadcastAddress", "Dirección de broadcast"),
|
|
("Nodes", "Hosts disponibles"),
|
|
("mask", "Objeto máscara con sus métodos"),
|
|
("get_netmask_str", "Obtiene la máscara de red (ej: 255.255.255.0)"),
|
|
("get_prefix_length", "Obtiene la longitud del prefijo CIDR (ej: 24)"),
|
|
]
|
|
|
|
def get_netmask_str(self) -> Optional[str]:
|
|
return self._mask_obj.get_mask_str() if self._mask_obj else None
|
|
|
|
def get_prefix_length(self) -> Optional[int]:
|
|
return self._mask_obj.get_prefix_int() if self._mask_obj else None
|
|
|
|
def NetworkAddress(self):
|
|
prefix = self.get_prefix_length()
|
|
if prefix is None:
|
|
raise ValueError("No prefix/mask defined for NetworkAddress calculation.")
|
|
|
|
mask_int = IP4Mask._prefix_to_mask_int(prefix)
|
|
network_int = self._ip_int & mask_int
|
|
|
|
parts = [
|
|
(network_int >> 24) & 0xFF,
|
|
(network_int >> 16) & 0xFF,
|
|
(network_int >> 8) & 0xFF,
|
|
network_int & 0xFF
|
|
]
|
|
network_str = '.'.join(str(x) for x in parts)
|
|
return Class_IP4(network_str, prefix)
|
|
|
|
def BroadcastAddress(self):
|
|
prefix = self.get_prefix_length()
|
|
if prefix is None:
|
|
raise ValueError("No prefix/mask defined for BroadcastAddress calculation.")
|
|
|
|
mask_int = IP4Mask._prefix_to_mask_int(prefix)
|
|
broadcast_int = self._ip_int | (~mask_int & 0xFFFFFFFF)
|
|
|
|
parts = [
|
|
(broadcast_int >> 24) & 0xFF,
|
|
(broadcast_int >> 16) & 0xFF,
|
|
(broadcast_int >> 8) & 0xFF,
|
|
broadcast_int & 0xFF
|
|
]
|
|
broadcast_str = '.'.join(str(x) for x in parts)
|
|
return Class_IP4(broadcast_str, prefix)
|
|
|
|
def Nodes(self):
|
|
prefix = self.get_prefix_length()
|
|
if prefix is None:
|
|
raise ValueError("No prefix/mask defined for Nodes calculation.")
|
|
|
|
if prefix >= 31:
|
|
return 0
|
|
return (2 ** (32 - prefix)) - 2
|
|
|
|
def mask(self) -> Optional[IP4Mask]:
|
|
"""Retorna objeto máscara para autocompletado"""
|
|
if not self._mask_obj:
|
|
raise ValueError("No mask defined for this IP4 object.")
|
|
return self._mask_obj |