181 lines
5.9 KiB
Python
181 lines
5.9 KiB
Python
"""
|
|
Clase híbrida para direcciones IPv4
|
|
"""
|
|
from sympy_Base import SympyClassBase
|
|
from typing import Optional, Tuple
|
|
import re
|
|
|
|
|
|
class Class_IP4(SympyClassBase):
|
|
"""Clase híbrida para direcciones IPv4"""
|
|
|
|
def __new__(cls, *args):
|
|
"""Crear objeto SymPy válido"""
|
|
obj = SympyClassBase.__new__(cls)
|
|
return obj
|
|
|
|
def __init__(self, *args):
|
|
"""Inicialización de IP4"""
|
|
if len(args) == 1:
|
|
# Formato: "192.168.1.1/24" o "192.168.1.1 255.255.255.0"
|
|
input_str = args[0]
|
|
if '/' in input_str:
|
|
# Formato CIDR
|
|
ip_str, prefix_str = input_str.split('/')
|
|
prefix = int(prefix_str)
|
|
else:
|
|
# Formato con máscara
|
|
parts = input_str.split()
|
|
if len(parts) == 2:
|
|
ip_str, netmask_str = parts
|
|
prefix = self._netmask_str_to_prefix(netmask_str)
|
|
else:
|
|
ip_str = input_str
|
|
prefix = None
|
|
else:
|
|
# Formato: ("192.168.1.1", 24) o ("192.168.1.1", "255.255.255.0")
|
|
ip_str = args[0]
|
|
if len(args) > 1:
|
|
if isinstance(args[1], int):
|
|
prefix = args[1]
|
|
else:
|
|
prefix = self._netmask_str_to_prefix(args[1])
|
|
else:
|
|
prefix = None
|
|
|
|
if not self._is_valid_ip_string(ip_str):
|
|
raise ValueError(f"Invalid IP address format: {ip_str}")
|
|
|
|
if prefix is not None and not self._is_valid_prefix(prefix):
|
|
raise ValueError(f"Invalid prefix length: {prefix}")
|
|
|
|
# Convertir IP a entero para almacenamiento
|
|
ip_parts = [int(x) for x in ip_str.split('.')]
|
|
ip_int = (ip_parts[0] << 24) + (ip_parts[1] << 16) + (ip_parts[2] << 8) + ip_parts[3]
|
|
|
|
# Almacenar valores
|
|
self._ip_int = ip_int
|
|
self._prefix = prefix
|
|
self._ip_str = ip_str
|
|
|
|
# Llamar al constructor base
|
|
super().__init__(ip_int, input_str)
|
|
|
|
def _is_valid_ip_string(self, ip_str: str) -> bool:
|
|
"""Verifica si el string es una IP válida"""
|
|
pattern = r'^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$'
|
|
match = re.match(pattern, ip_str)
|
|
if not match:
|
|
return False
|
|
|
|
# Verificar que cada octeto esté en rango
|
|
for octet in match.groups():
|
|
if not 0 <= int(octet) <= 255:
|
|
return False
|
|
|
|
return True
|
|
|
|
def _is_valid_prefix(self, prefix: int) -> bool:
|
|
"""Verifica si el prefijo es válido"""
|
|
return 0 <= prefix <= 32
|
|
|
|
def _netmask_str_to_prefix(self, netmask_str: str) -> Optional[int]:
|
|
"""Convierte máscara de red a longitud de prefijo"""
|
|
if not self._is_valid_ip_string(netmask_str):
|
|
return None
|
|
|
|
# Convertir máscara a binario
|
|
parts = [int(x) for x in netmask_str.split('.')]
|
|
binary = ''.join(f'{x:08b}' for x in parts)
|
|
|
|
# Contar 1's consecutivos desde la izquierda
|
|
prefix = 0
|
|
for bit in binary:
|
|
if bit == '1':
|
|
prefix += 1
|
|
else:
|
|
break
|
|
|
|
# Verificar que el resto sean 0's
|
|
if '1' in binary[prefix:]:
|
|
return None
|
|
|
|
return prefix
|
|
|
|
def __str__(self):
|
|
"""Representación string para display"""
|
|
if self._prefix is not None:
|
|
return f"{self._ip_str}/{self._prefix}"
|
|
return self._ip_str
|
|
|
|
def _sympystr(self, printer):
|
|
"""Representación SymPy string"""
|
|
return str(self)
|
|
|
|
@staticmethod
|
|
def Helper(input_str):
|
|
"""Ayuda contextual para IP4"""
|
|
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], o IP4[172.16.0.5, 255.255.0.0]\nFunciones: NetworkAddress(), BroadcastAddress(), Nodes()'
|
|
return None
|
|
|
|
@staticmethod
|
|
def PopupFunctionList():
|
|
"""Lista de métodos sugeridos para autocompletado de IP4"""
|
|
return [
|
|
("NetworkAddress", "Obtiene la dirección de red"),
|
|
("BroadcastAddress", "Obtiene la dirección de broadcast"),
|
|
("Nodes", "Cantidad de nodos disponibles"),
|
|
]
|
|
|
|
def NetworkAddress(self):
|
|
"""Obtiene la dirección de red"""
|
|
if self._prefix is None:
|
|
raise ValueError("No prefix/mask defined")
|
|
|
|
# Calcular máscara de red
|
|
mask = (0xffffffff >> (32 - self._prefix)) << (32 - self._prefix)
|
|
|
|
# Aplicar máscara
|
|
network = self._ip_int & mask
|
|
|
|
# Convertir a string
|
|
parts = [
|
|
(network >> 24) & 0xff,
|
|
(network >> 16) & 0xff,
|
|
(network >> 8) & 0xff,
|
|
network & 0xff
|
|
]
|
|
network_str = '.'.join(str(x) for x in parts)
|
|
|
|
return Class_IP4(f"{network_str}/{self._prefix}")
|
|
|
|
def BroadcastAddress(self):
|
|
"""Obtiene la dirección de broadcast"""
|
|
if self._prefix is None:
|
|
raise ValueError("No prefix/mask defined")
|
|
|
|
# Calcular máscara de red
|
|
mask = (0xffffffff >> (32 - self._prefix)) << (32 - self._prefix)
|
|
|
|
# Calcular broadcast
|
|
broadcast = self._ip_int | (~mask & 0xffffffff)
|
|
|
|
# Convertir a string
|
|
parts = [
|
|
(broadcast >> 24) & 0xff,
|
|
(broadcast >> 16) & 0xff,
|
|
(broadcast >> 8) & 0xff,
|
|
broadcast & 0xff
|
|
]
|
|
broadcast_str = '.'.join(str(x) for x in parts)
|
|
|
|
return Class_IP4(f"{broadcast_str}/{self._prefix}")
|
|
|
|
def Nodes(self):
|
|
"""Obtiene el número de nodos disponibles"""
|
|
if self._prefix is None:
|
|
raise ValueError("No prefix/mask defined")
|
|
|
|
# 2^(32-prefix) - 2 (red y broadcast)
|
|
return 2 ** (32 - self._prefix) - 2 |