Calc/ip4_type.py

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