""" Clase híbrida para direcciones IPv4 """ from hybrid_base import HybridCalcType from typing import Optional, Tuple import re class HybridIP4(HybridCalcType): """Clase híbrida para direcciones IPv4""" def __new__(cls, *args): """Crear objeto SymPy válido""" obj = HybridCalcType.__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""" return """ Formato IP4: - CIDR: 192.168.1.1/24 - Con máscara: 192.168.1.1 255.255.255.0 - Solo IP: 192.168.1.1 Métodos disponibles: - NetworkAddress(): Obtiene dirección de red - BroadcastAddress(): Obtiene dirección de broadcast - Nodes(): Obtiene número 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 HybridIP4(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 HybridIP4(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