Skip to main content
Loading...

More Python Posts

import logging
import os
import re
import subprocess
import time
import json
from typing import Optional, Tuple, List, Dict, Any
from glob import glob
from datetime import datetime


class BGPRouteParser():
    bgp_route_pattern = r'^\*>\s+(\S+)\s+(\S+)\s+(\d+)\s+(\d+)\s+(.+)$'


    def _normalize_network_cidr(self, network: str) -> str:
        """Normalize network address by adding appropriate CIDR notation.
        
        Args:
            network: Network address (e.g., "172.31.0.0" or "172.16.0.1/32")
            
        Returns:
            Network address with appropriate CIDR notation
        """
        if '/' in network:
            return network
            
        try:
            octets = network.split('.')
            if len(octets) != 4:
                return network  # Invalid IP format or IPv6, return as-is

            # Determine CIDR based on trailing zero pattern

            # Check for default route
            if network == '0.0.0.0':
                return '0.0.0.0/0'

            if octets[1:] == ['0', '0', '0']:
                return f"{network}/8"

            if octets[2:] == ['0', '0']:
                return f"{network}/16"

            if octets[3] == '0':
                return f"{network}/24"

        except (ValueError, IndexError):
            return network
        

    def _parse_as_path(self, path_info: str) -> str:
        """Extract AS path from BGP path information.
        
        Args:
            path_info: Raw path information from BGP output
            
        Returns:
            Cleaned AS path string
        """
        path_info = path_info.strip()
        
        # Handle internal routes
        if path_info == 'i':
            return str(self.headend_config.local_bgp_asn)
            
        # Extract AS numbers using list comprehension
        path_parts = path_info.split()
        as_numbers = [part for part in path_parts if part.isdigit()]
        
        return ' '.join(as_numbers)

    def _parse_route_line(self, line: str) -> Optional[Dict[str, Any]]:
        """Parse a single BGP route line (IPv4 format).
        
        Args:
            line: BGP route line from show command output
            
        Returns:
            Route dictionary or None if parsing fails
        """
        
        match = re.match(self.bgp_route_pattern, line)
        if not match:
            return None
            
        network, next_hop, metric_str, weight_str, path_info = match.groups()
        
        try:
            return {
                "network": self._normalize_network_cidr(network),
                "nextHopIp": next_hop,
                "med": int(metric_str),
                "localPref": 100, # Always 100 for learned routes on PEs
                "weight": int(weight_str),
                "asPath": self._parse_as_path(path_info)
            }
        except ValueError as e:
            logging.warning(f"Failed to parse route line '{line}': {e}")
            return None

    def _parse_ipv6_route_block(self, lines: List[str], start_idx: int) -> Tuple[Optional[Dict[str, Any]], int]:
        """Parse a multi-line IPv6 BGP route block.
        
        Args:
            lines: List of all output lines
            start_idx: Index of the first line of the route block
            
        Returns:
            Tuple of (route dictionary or None, next line index to process)
        """
        if start_idx >= len(lines):
            return None, start_idx + 1
            
        # First line: *> network
        first_line = lines[start_idx].strip()
        if not first_line.startswith('*>'):
            return None, start_idx + 1
            
        # Extract network from first line
        network_match = re.match(r'^\*>\s+(\S+)$', first_line)
        if not network_match:
            return None, start_idx + 1
            
        network = network_match.group(1)
        
        # Second line: next hop (indented)
        if start_idx + 1 >= len(lines):
            return None, start_idx + 1
            
        second_line = lines[start_idx + 1].strip()
        if not second_line or second_line.startswith('*>'):
            # This might be a single-line IPv6 route or malformed
            return None, start_idx + 1
            
        next_hop = second_line
        
        # Third line: metric, locprf, weight, path (indented)
        if start_idx + 2 >= len(lines):
            return None, start_idx + 2
            
        third_line = lines[start_idx + 2].strip()
        if not third_line or third_line.startswith('*>'):
            # Malformed route block
            return None, start_idx + 2
            
        # Parse the third line: metric locprf weight path
        path_parts = third_line.split()
        if len(path_parts) < 4:
            return None, start_idx + 3
            
        try:
            metric_str = path_parts[0]
            # Skip locprf (path_parts[1]) as it's empty or not used
            weight_str = path_parts[-2]  # Second to last element
            path_info = path_parts[-1]   # Last element
            
            return {
                "network": self._normalize_network_cidr(network),
                "nextHopIp": next_hop,
                "med": int(metric_str),
                "localPref": 100, # Always 100 for learned routes on PEs
                "weight": int(weight_str),
                "asPath": self._parse_as_path(path_info)
            }, start_idx + 3
            
        except (ValueError, IndexError) as e:
            logging.warning(f"Failed to parse IPv6 route block starting at line {start_idx}: {e}")
            return None, start_idx + 3

    def _find_route_start_index(self, lines: List[str]) -> Optional[int]:
        """Find the index where BGP routes start in the output.
        
        Args:
            lines: List of output lines
            
        Returns:
            Index of first route line or None if not found
        """
        for i, line in enumerate(lines):
            if 'Network' in line and 'Next Hop' in line:
                return i + 1
        return None

    def _get_ipv4_bgp_routes(self) -> List[Dict[str, Any]]:
        """Parse IPv4 BGP route table output and return structured data.
        
        Returns:
            List of IPv4 BGP routes
        """
        try:
            output = """BGP table version is 0, local router ID is 169.254.148.249
    Status codes: s suppressed, d damped, h history, * valid, > best, i - internal,
                r RIB-failure, S Stale, R Removed
    Origin codes: i - IGP, e - EGP, ? - incomplete

    Network          Next Hop            Metric LocPrf Weight Path
    *> 172.16.0.1/32   169.254.50.85         0             0 65000 i


    Total number of prefixes 2"""
            
            # Check if command output is valid
            if not output or not output.strip():
                logging.warning("IPv4 BGP command returned empty output")
                return []
            
            lines = output.strip().split('\n')
            route_start_idx = self._find_route_start_index(lines)
            
            if route_start_idx is None:
                logging.warning("IPv4 BGP output does not contain expected header format")
                return []
            
            # Parse routes using list comprehension and filter
            route_lines = [
                line.strip() for line in lines[route_start_idx:]
                if line.strip() and line.strip().startswith('*>') 
                and not line.strip().startswith('Total number')
            ]
            
            # Parse routes and convert to dictionaries
            routes = []
            for line in route_lines:
                route = self._parse_route_line(line)
                if route is not None:
                    routes.append(route)
            
            return routes
            
        except Exception as e:
            logging.error(f"Failed to get IPv4 BGP routes: {e}")
            return []

    def _get_ipv6_bgp_routes(self) -> List[Dict[str, Any]]:
        """Parse IPv6 BGP route table output and return structured data.
        
        Returns:
            List of IPv6 BGP routes
        """
        try:
            output = """BGP table version is 0, local router ID is 169.254.148.249
    Status codes: s suppressed, d damped, h history, * valid, > best, i - internal,
                r RIB-failure, S Stale, R Removed
    Origin codes: i - IGP, e - EGP, ? - incomplete

    Network          Next Hop            Metric LocPrf Weight Path
    *> 2600:1702:5fc0:8792::/64
                        fd1c:a349:ac38:7ea1:792e:e716:baa1:52b6
                                                0             0 65000 i
    *> 2600:4700:4700::1111/128
                        fd1c:a349:ac38:7ea1:792e:e716:baa1:52b5
                                            100         32768 i

    Total number of prefixes 2"""
            
            # Check if command output is valid
            if not output or not output.strip():
                logging.warning("IPv6 BGP command returned empty output")
                return []
            
            lines = output.strip().split('\n')
            route_start_idx = self._find_route_start_index(lines)
            
            if route_start_idx is None:
                logging.warning("IPv6 BGP output does not contain expected header format")
                return []
            
            # Parse IPv6 routes (multi-line format)
            routes = []
            i = route_start_idx
            
            while i < len(lines):
                line = lines[i].strip()
                
                # Skip empty lines and total count lines
                if not line or line.startswith('Total number'):
                    i += 1
                    continue
                    
                # Process route blocks starting with *>
                if line.startswith('*>'):
                    route, next_idx = self._parse_ipv6_route_block(lines, i)
                    if route is not None:
                        routes.append(route)
                    i = next_idx
                else:
                    i += 1
            
            return routes
            
        except Exception as e:
            logging.error(f"Failed to get IPv6 BGP routes: {e}")
            return []

    def _get_all_bgp_routes(self) -> Dict[str, List[Dict[str, Any]]]:
        """Parse both IPv4 and IPv6 BGP route table outputs and return structured data.
        
        Returns:
            Dictionary containing list of all BGP routes (IPv4 and IPv6 combined)
        """
        try:
            # Get IPv4 routes
            ipv4_routes = self._get_ipv4_bgp_routes()
            
            # Get IPv6 routes
            ipv6_routes = self._get_ipv6_bgp_routes()
            
            # Combine all routes
            all_routes = ipv4_routes + ipv6_routes
            
            logging.debug(f"Retrieved {len(ipv4_routes)} IPv4 routes and {len(ipv6_routes)} IPv6 routes")
            
            return {"routes": all_routes}
            
        except Exception as e:
            logging.error(f"Failed to get all BGP routes: {e}")
            return {"routes": []}

    def _get_specific_network(self, bgp_properties: Dict[str, Any], prefix: str) -> Dict[str, Any]:
        """Find specific network in BGP properties.
        
        Args:
            bgp_properties: BGP properties dictionary
            prefix: Network prefix to search for
            
        Returns:
            Route information dictionary or empty dict if not found
        """
        if not bgp_properties or not isinstance(bgp_properties, dict):
            logging.warning(f"Invalid BGP properties: {type(bgp_properties)}")
            return {}
            
        routes = bgp_properties.get("routes", [])
        logging.debug(f"Searching for prefix '{prefix}' in {len(routes)} routes")
        
        # Use next() with generator expression for efficient search
        try:
            route = next(
                route for route in routes 
                if route.get("network") == prefix
            )
            logging.debug(f"Found matching route for prefix '{prefix}': {route}")
            return route
        except StopIteration:
            logging.debug(f"No route found for prefix '{prefix}'")
            return {}


def main():
    """Main function to demonstrate BGP route parsing."""
    # Configure logging
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(levelname)s - %(message)s'
    )
    
    # Create parser instance
    parser = BGPRouteParser()
    
    # Get BGP routes
    print("Parsing BGP routes...")
    routes_data = parser._get_all_bgp_routes()
    
    # Display results
    print(f"Found {len(routes_data['routes'])} BGP routes:")
    print("-" * 50)
    
    for i, route in enumerate(routes_data['routes'], 1):
        print(f"Route {route}:")

    
    return routes_data


if __name__ == "__main__":
    main()
class ProposalParser:
    """A class to parse IKE and ESP proposal strings into human-readable formats.

    This class supports parsing of IKE and ESP proposals, extracting encryption, hash, PRF (for IKE),
    and Diffie-Hellman (DH) group information. It also handles the concatenation of these components
    into a structured format, indicating whether Perfect Forward Secrecy (PFS) is enabled for ESP proposals.
    The parser uses predefined mappings for DH groups, encryption algorithms, hash functions, and Pseudo-Random Functions (PRFs).
    It can process a list of proposals and return a formatted string summarizing the cryptographic parameters.

    Attributes:
        dh_mapping (dict): A mapping of Diffie-Hellman groups to their corresponding identifiers
        enc_mapping (dict): A mapping of encryption algorithms to their corresponding identifiers
        hash_mapping (dict): A mapping of hash functions to their corresponding identifiers
        prf_mapping (dict): A mapping of Pseudo-Random Functions to their corresponding identifiers
    """

    def __init__(self):
        """Initialize the parser with mappings for DH groups, encryption, hash, and PRF."""
        self.dh_mapping = {
            'MODP_768': '1',  # RFC 2409: 768-bit MODP group (Group 1), considered weak by modern standards
            'MODP_1024': '2',  # RFC 2409: 1024-bit MODP group (Group 2), considered weak today
            'MODP_1536': '5',  # RFC 3526: 1536-bit MODP group (Group 5), stronger than Groups 1 and 2
            'MODP_2048': '14',  # RFC 3526: 2048-bit MODP group (Group 14), commonly used for modern IKE
            'MODP_3072': '15',  # RFC 3526: 3072-bit MODP group (Group 15), high security
            'MODP_4096': '16',  # RFC 3526: 4096-bit MODP group (Group 16), suitable for high-security applications
            'MODP_6144': '17',  # RFC 3526: 6144-bit MODP group (Group 17), very high security, less common
            'MODP_8192': '18',  # RFC 3526: 8192-bit MODP group (Group 18), highest MODP group, rarely used
            'ECP_256': '19',  # RFC 5903: 256-bit ECP group (NIST P-256, Group 19), efficient elliptic curve
            'ECP_384': '20',  # RFC 5903: 384-bit ECP group (NIST P-384, Group 20), stronger elliptic curve
            'ECP_521': '21',  # RFC 5903: 521-bit ECP group (NIST P-521, Group 21), high-security elliptic curve
            'ECP_192': '25',  # RFC 5903: 192-bit ECP group (NIST P-192, Group 25), weaker elliptic curve
            'ECP_224': '26',  # RFC 5903: 224-bit ECP group (NIST P-224, Group 26), intermediate security
            'MODP_1024_160': '22',  # RFC 5114: 1024-bit MODP with 160-bit subgroup (Group 22), less common
            'MODP_2048_224': '23',  # RFC 5114: 2048-bit MODP with 224-bit subgroup (Group 23), enhanced security
            'MODP_2048_256': '24',  # RFC 5114: 2048-bit MODP with 256-bit subgroup (Group 24), enhanced security
            'FFDHE_2048': '256',  # RFC 7919: 2048-bit FFDHE group, secure Diffie-Hellman parameters
            'FFDHE_3072': '257',  # RFC 7919: 3072-bit FFDHE group, secure Diffie-Hellman parameters
            'FFDHE_4096': '258',  # RFC 7919: 4096-bit FFDHE group, secure Diffie-Hellman parameters
            'FFDHE_6144': '259',  # RFC 7919: 6144-bit FFDHE group, secure Diffie-Hellman parameters
            'FFDHE_8192': '260',  # RFC 7919: 8192-bit FFDHE group, secure Diffie-Hellman parameters
            'ECP_224_BP': '27',  # RFC 6460: 224-bit Brainpool ECP group, alternative elliptic curve
            'ECP_256_BP': '28',  # RFC 6460: 256-bit Brainpool ECP group, alternative elliptic curve
            'ECP_384_BP': '29',  # RFC 6460: 384-bit Brainpool ECP group, alternative elliptic curve
            'ECP_512_BP': '30',  # RFC 6460: 512-bit Brainpool ECP group, alternative elliptic curve
            'CURVE_25519': '31',  # RFC 8031: Curve25519, modern elliptic curve for high-speed cryptography
            'CURVE_448': '32',  # RFC 8031: Curve448, modern elliptic curve for high-security cryptography
        }

        self.enc_mapping = {
            'AES_CBC_128': 'AES128',  # RFC 3602: AES-CBC with 128-bit key, standard encryption for IPsec
            'AES_CBC_192': 'AES192',  # RFC 3602: AES-CBC with 192-bit key, less common but supported
            'AES_CBC_256': 'AES256',  # RFC 3602: AES-CBC with 256-bit key, widely used for high security
            'AES_GCM_16_128': 'AES128-GCM-16',  # RFC 4106: AES-GCM with 128-bit key, 16-byte ICV
            'AES_GCM_16_192': 'AES192-GCM-16',  # RFC 4106: AES-GCM with 192-bit key, 16-byte ICV
            'AES_GCM_16_256': 'AES256-GCM-16',  # RFC 4106: AES-GCM with 256-bit key, 16-byte ICV
            'AES_GCM_8_128': 'AES128-GCM-8',  # RFC 4106: AES-GCM with 128-bit key, 8-byte ICV
            'AES_GCM_8_256': 'AES256-GCM-8',  # RFC 4106: AES-GCM with 256-bit key, 8-byte ICV
            'AES_GCM_12_128': 'AES128-GCM-12',  # RFC 4106: AES-GCM with 128-bit key, 12-byte ICV
            'AES_GCM_12_256': 'AES256-GCM-12',  # RFC 4106: AES-GCM with 256-bit key, 12-byte ICV
            'AES_CCM_16_128': 'AES128-CCM-16',  # RFC 4309: AES-CCM with 128-bit key, 16-byte ICV
            'AES_CCM_16_256': 'AES256-CCM-16',  # RFC 4309: AES-CCM with 256-bit key, 16-byte ICV
            'AES_CTR_128': 'AES128-CTR',  # RFC 3686: AES-CTR with 128-bit key, stream cipher mode
            'AES_CTR_192': 'AES192-CTR',  # RFC 3686: AES-CTR with 192-bit key, stream cipher mode
            'AES_CTR_256': 'AES256-CTR',  # RFC 3686: AES-CTR with 256-bit key, stream cipher mode
            '3DES_CBC': '3DES',  # RFC 2451: Triple DES in CBC mode, legacy encryption
            'DES_CBC': 'DES',  # RFC 2405: DES in CBC mode, considered insecure today
            'CAMELLIA_CBC_128': 'CAMELLIA128',  # RFC 5529: Camellia-CBC with 128-bit key
            'CAMELLIA_CBC_256': 'CAMELLIA256',  # RFC 5529: Camellia-CBC with 256-bit key
            'CHACHA20_POLY1305': 'CHACHA20-POLY1305',  # RFC 7634: ChaCha20 with Poly1305, modern AEAD cipher
            'BLOWFISH_CBC': 'BLOWFISH',  # RFC 2451: Blowfish in CBC mode, legacy and less secure
            'CAST5_CBC': 'CAST5',  # RFC 2144: CAST-128 in CBC mode, legacy encryption
        }

        self.hash_mapping = {
            'HMAC_MD5': 'MD5',  # RFC 2403: HMAC-MD5, considered weak by modern standards
            'HMAC_MD5_96': 'MD5',  # RFC 2403: HMAC-MD5 with 96-bit truncation, weak
            'HMAC_SHA1': 'SHA1',  # RFC 2404: HMAC-SHA1, widely used but aging
            'HMAC_SHA1_96': 'SHA1',  # RFC 2404: HMAC-SHA1 with 96-bit truncation
            'HMAC_SHA2_256': 'SHA2-256',  # RFC 4868: HMAC-SHA256, strong hash function
            'HMAC_SHA2_256_128': 'SHA2-256',  # RFC 4868: HMAC-SHA256 with 128-bit truncation
            'HMAC_SHA2_384': 'SHA2-384',  # RFC 4868: HMAC-SHA384, stronger hash function
            'HMAC_SHA2_384_192': 'SHA2-384',  # RFC 4868: HMAC-SHA384 with 192-bit truncation
            'HMAC_SHA2_512': 'SHA2-512',  # RFC 4868: HMAC-SHA512, very strong hash function
            'HMAC_SHA2_512_256': 'SHA2-512',  # RFC 4868: HMAC-SHA512 with 256-bit truncation
            'HMAC_SHA3_224': 'SHA3-224',  # RFC 8446 (TLS context): SHA3-224, modern hash function, experimental for the most part
            'HMAC_SHA3_256': 'SHA3-256',  # RFC 8446 (TLS context): SHA3-256, modern hash function, experimental for the most part
            'HMAC_SHA3_384': 'SHA3-384',  # RFC 8446 (TLS context): SHA3-384, modern hash function, experimental for the most part
            'HMAC_SHA3_512': 'SHA3-512',  # RFC 8446 (TLS context): SHA3-512, modern hash function, experimental for the most part
            'AES_GMAC_128': 'GMAC-128',  # RFC 4543: AES-GMAC with 128-bit key, for authentication
            'AES_GMAC_192': 'GMAC-192',  # RFC 4543: AES-GMAC with 192-bit key, for authentication
            'AES_GMAC_256': 'GMAC-256',  # RFC 4543: AES-GMAC with 256-bit key, for authentication
            'POLY1305': 'POLY1305',  # RFC 7539: Poly1305, used with ChaCha20 for authentication
        }

        self.prf_mapping = {
            'PRF_HMAC_MD5': 'MD5',  # RFC 2403 (via IKEv1): HMAC-MD5 PRF, weak by modern standards
            'PRF_HMAC_SHA1': 'SHA1',  # RFC 2404 (via IKEv1): HMAC-SHA1 PRF, widely used but aging
            'PRF_HMAC_SHA2_256': 'SHA2-256',  # RFC 4868: HMAC-SHA256 PRF, strong and common
            'PRF_HMAC_SHA2_384': 'SHA2-384',  # RFC 4868: HMAC-SHA384 PRF, stronger PRF
            'PRF_HMAC_SHA2_512': 'SHA2-512',  # RFC 4868: HMAC-SHA512 PRF, very strong PRF
            'PRF_AES128_CMAC': 'AES128-CMAC',  # RFC 4494: AES-CMAC with 128-bit key, secure PRF
            'PRF_AES128_XCBC': 'AES128-XCBC',  # RFC 4434: AES-XCBC PRF, alternative to CMAC
            'PRF_HMAC_SHA3_224': 'SHA3-224',  # RFC 8446 (TLS context): SHA3-224 PRF, modern algorithm, experimental for the most part
            'PRF_HMAC_SHA3_256': 'SHA3-256',  # RFC 8446 (TLS context): SHA3-256 PRF, modern algorithm, experimental for the most part
            'PRF_HMAC_SHA3_384': 'SHA3-384',  # RFC 8446 (TLS context): SHA3-384 PRF, modern algorithm, experimental for the most part
            'PRF_HMAC_SHA3_512': 'SHA3-512',  # RFC 8446 (TLS context): SHA3-512 PRF, modern algorithm, experimental for the most part
        }

    def _categorize_component(self, component, is_ike):
        """Categorize a proposal component into encryption, hash, PRF, or DH group."""
        enc_keywords = ['AES_CBC', 'AES_GCM', 'AES_CTR', 'CHACHA20', 'BLOWFISH', 'CAST5', 'DES', '3DES', 'CAMELLIA']
        hash_keywords = ['HMAC_SHA', 'HMAC_MD5', 'POLY1305', 'AES_GMAC']
        dh_keywords = ['MODP_', 'ECP_', 'FFDHE_', 'CURVE_']

        if (component in self.enc_mapping or any(s in component for s in enc_keywords)):
            return 'encryption', component
        elif is_ike and 'PRF_' in component:
            return 'prf', component
        elif (component in self.hash_mapping or any(s in component for s in hash_keywords)) and not component.startswith("PRF_"):
            return 'hash', component
        elif (component in self.dh_mapping or any(s in component for s in dh_keywords)):
            return 'dh_group', component
        elif component == 'NO_EXT_SEQ':
            return 'skip', component
        else:
            return 'unknown', component

    def _map_encryption(self, enc_components, result):
        """Map encryption components to their corresponding identifiers."""
        for enc in enc_components:
            mapped_enc = self.enc_mapping.get(enc, 'Unknown')
            if mapped_enc != 'Unknown' and mapped_enc not in result['encryption']:
                result['encryption'].append(mapped_enc)

    def _map_hash_and_prf(self, enc_components, hash_components, prf_components, result):
        """Map hash and PRF components, handling AEAD ciphers."""
        # Map hash components (skip for AEAD ciphers like AES-GCM, AES-CCM, CHACHA20-POLY1305)
        if not any(enc.startswith('AES_GCM') or enc.startswith('AES_CCM') or enc == 'CHACHA20_POLY1305' for enc in enc_components):
            for hash_val in hash_components:
                mapped_hash = self.hash_mapping.get(hash_val, 'Unknown')
                if mapped_hash not in result['hash']:
                    result['hash'].append(mapped_hash)
        else:
            result['hash'].append('None')

        # Map PRF components
        for prf in prf_components:
            mapped_prf = self.prf_mapping.get(prf, 'Unknown')
            if mapped_prf not in result['prf']:
                result['prf'].append(mapped_prf)

    def _map_dh_group(self, dh_components, result):
        """Map Diffie-Hellman group components to their corresponding identifiers."""
        for dh in dh_components:
            mapped_dh = self.dh_mapping.get(dh)
            if mapped_dh != 'None' and mapped_dh not in result['dh_group']:
                result['dh_group'].append(mapped_dh)

    def parse_ike_proposal(self, proposal):
        """
        Parse an IKE or ESP proposal string into a structured format.

        Args:
            proposal (str): The proposal string, e.g., "IKE:AES_CBC_256/HMAC_SHA2_256/PRF_HMAC_SHA2_256/MODP_2048"

        Returns:
            dict: A dictionary with keys 'encryption', 'hash', 'prf', and 'dh_group'
        """
        # Split the proposal into components based on '/'
        components = proposal.split('/')

        result = {
            'encryption': [],
            'hash': [],
            'prf': [],
            'dh_group': []
        }

        is_ike = proposal.startswith('IKE:')
        is_esp = proposal.startswith('ESP:')

        # Remove IKE or ESP prefix if present for easier parsing
        if is_ike or is_esp:
            components[0] = components[0].replace('IKE:', '').replace('ESP:', '')

        enc_components = []
        hash_components = []
        prf_components = []
        dh_components = []
        unknown_components = []

        # Categorize components
        for component in components:
            category, value = self._categorize_component(component, is_ike)
            if category == 'encryption':
                enc_components.append(value)
            elif category == 'hash':
                hash_components.append(value)
            elif category == 'prf':
                prf_components.append(value)
            elif category == 'dh_group':
                dh_components.append(value)
            elif category == 'unknown':
                unknown_components.append(value)

        # Map components to their identifiers
        self._map_encryption(enc_components, result)
        self._map_hash_and_prf(enc_components, hash_components, prf_components, result)
        self._map_dh_group(dh_components, result)

        # Handle ESP case (no PRF for ESP proposals)
        if is_esp:
            result['prf'] = ['None']

        # Set defaults if no valid components found
        if not result['encryption']:
            result['encryption'] = ['Unknown']
        if not result['hash']:
            result['hash'] = ['None']
        if not result['prf']:
            result['prf'] = ['None']
        if not result['dh_group']:
            result['dh_group'] = ['None']

        return result

    def collect_components(self, proposals):
        """Collect unique encryption, hash, PRF, and DH group components from a list of proposals."""
        enc_set = set()
        hash_set = set()
        prf_set = set()
        dh_set = set()
        all_aead = True

        for proposal in proposals:
            parsed = self.parse_ike_proposal(proposal.strip())
            enc_set.update(parsed['encryption'])
            
            # Check if the proposal uses a non-AEAD cipher
            if not any('GCM' in enc or 'CCM' in enc or 'CHACHA20' in enc for enc in parsed['encryption']):
                hash_set.update(parsed['hash'])
                all_aead = False
            prf_set.update(parsed['prf'])
            if parsed['dh_group'] != ['None']:
                dh_set.update(parsed['dh_group'])

        # If all proposals are AEAD, set hash to "None"
        if all_aead and not hash_set:
            hash_set.add('None')

        return enc_set, hash_set, prf_set, dh_set

    def format_output(self, enc_set, hash_set, prf_set, dh_set, is_ike):
        """Format the collected components into a human-readable string."""
        # Convert sets to sorted lists
        enc_list = sorted(list(enc_set))
        hash_list = sorted(list(hash_set))
        prf_list = sorted(list(prf_set))
        dh_list = sorted(list(dh_set), key=lambda x: int(x))

        # Determine PFS status for ESP proposals only
        pfs_status = "PFS: Enabled" if dh_set and not is_ike else "PFS: None"

        # Format output as a single concatenated string
        enc_part = f"Encryption: {', '.join(enc_list)}" if enc_list else "Encryption: None"
        hash_part = f"Hash: {', '.join(hash_list)}" if hash_list else "Hash: None"
        dh_part = f"DH Group(s): {', '.join(dh_list)}" if dh_list else "DH Group(s): None"
        prf_part = f"PRF: {', '.join(prf_list)}" if prf_list else "PRF: None"

        # Return formatted string based on whether it's an IKE or ESP proposal
        if is_ike:
            return f"{enc_part} {hash_part} {prf_part} {dh_part}"
        else:
            return f"{enc_part} {hash_part} {dh_part} {pfs_status}"

    def process_proposals(self, proposal_list):
        """
        Process a list of IKE or ESP proposals, concatenating encryption, hash, PRF (for IKE only),
        and DH group values, and indicate whether PFS is enabled for ESP proposals only.

        Args:
            proposal_list (str): Comma-separated string of IKE or ESP proposals

        Returns:
            str: Formatted string with concatenated encryption, hash, PRF (for IKE), DH groups, and PFS status (for ESP)
        """
        if proposal_list is None:
            return "No proposals provided"

        if not proposal_list:
            return "No proposals provided"

        if not (proposal_list.startswith('IKE:') or proposal_list.startswith('ESP:')):
            return "Invalid proposal format. Proposals must be of type 'IKE' for Phase 1 or 'ESP' for Phase 2"

        proposal_list = proposal_list.replace(',', ', ')
        proposals = proposal_list.strip().split(', ')
        is_ike = any(proposal.startswith('IKE:') for proposal in proposals)

        enc_set, hash_set, prf_set, dh_set = self.collect_components(proposals)
        return self.format_output(enc_set, hash_set, prf_set, dh_set, is_ike)
# Example usage
if __name__ == "__main__":
    parser = ProposalParser()

    # unknown_hash = "IKE:AES_CBC_256/HMAC_SHA22222/PRF_HMAC_SHA2_256/MODP_2048"
    # none_hash = "IKE:AES_CBC_256/PRF_HMAC_SHA2_256/MODP_2048"

    # # Outputs: Encryption: AES256 Hash: None PRF: SHA2-256 DH Group(s): 14
    # print(parser.process_proposals(unknown_hash))

    # # Outputs: Encryption: AES256 Hash: None PRF: SHA2-256 DH Group(s): 14
    # print(parser.process_proposals(none_hash))

    proposal = "IKE:AES_GCM_16_256/MODP_2048/NO_EXT_SEQ"

    print(parser.process_proposals(proposal))
class ProposalParser:
    """A class to parse IKE and ESP proposal strings into human-readable formats.

    This class supports parsing of IKE and ESP proposals, extracting encryption, hash, PRF (for IKE),
    and Diffie-Hellman (DH) group information. It also handles the concatenation of these components
    into a structured format, indicating whether Perfect Forward Secrecy (PFS) is enabled for ESP proposals.
    The parser uses predefined mappings for DH groups, encryption algorithms, hash functions, and Pseudo-Random Functions (PRFs).
    It can process a list of proposals and return a formatted string summarizing the cryptographic parameters.
    Attributes:

        dh_mapping (dict): A mapping of Diffie-Hellman groups to their corresponding identifiers

        enc_mapping (dict): A mapping of encryption algorithms to their corresponding identifiers

        hash_mapping (dict): A mapping of hash functions to their corresponding identifiers

        prf_mapping (dict): A mapping of Pseudo-Random Functions to their corresponding identifiers

    Methods:
        parse_ike_proposal(proposal): Parses a single IKE or ESP proposal string into a structured dictionary with encryption, hash, PRF, and DH group information.

        process_proposals(proposal_list): Processes a comma-separated list of IKE or ESP proposals, concatenating encryption, hash, PRF (for IKE), and DH group values, and indicating whether PFS is enabled for ESP proposals.
    
    Usage:
        _proposal_parser = ProposalParser()
        proposal = "IKE:AES_CBC_256/HMAC_SHA2_256/PRF_HMAC_SHA2_256/MODP_2048"
        parsed_proposal = _proposal_parser.parse_ike_proposal(proposal)
        print(parsed_proposal)
        # Output: {'encryption': ['AES256'], 'hash': ['SHA2-256'], 'prf': ['SHA2-256'], 'dh_group': ['14']}
        proposal_list = "IKE:AES_CBC_256/HMAC_SHA2_256/PRF_HMAC_SHA2_256/MODP_2048, ESP:AES_GCM_16_256/HMAC_SHA2_256/MODP_2048"
        formatted_proposal = _proposal_parser.process_proposals(proposal_list)
        print(formatted_proposal)ss
        # Output: "Encryption: AES256, AES128-GCM-16 Hash: SHA2-256, PRF: SHA2-256, DH Group(s): 14 PFS: Enabled"
    """
    
    def __init__(self):
        """Initialize the parser with mappings for DH groups, encryption, hash, and PRF."""
        self.dh_mapping = {
            'MODP_768': '1',
            'MODP_1024': '2',
            'MODP_1536': '5',
            'MODP_2048': '14',
            'MODP_3072': '15',
            'MODP_4096': '16',
            'MODP_6144': '17',
            'MODP_8192': '18',
            'ECP_256': '19',
            'ECP_384': '20',
            'ECP_521': '21',
            'ECP_192': '25',
            'ECP_224': '26',
            'MODP_1024_160': '22',
            'MODP_2048_224': '23',
            'MODP_2048_256': '24',
            'FFDHE_2048': '256',
            'FFDHE_3072': '257',
            'FFDHE_4096': '258',
            'FFDHE_6144': '259',
            'FFDHE_8192': '260',
            'ECP_224_BP': '27',
            'ECP_256_BP': '28',
            'ECP_384_BP': '29',
            'ECP_512_BP': '30',
            'CURVE_25519': '31',
            'CURVE_448': '32',
        }
        
        self.enc_mapping = {
            'AES_CBC_128': 'AES128',
            'AES_CBC_192': 'AES192',
            'AES_CBC_256': 'AES256',
            'AES_GCM_16_128': 'AES128-GCM-16',
            'AES_GCM_16_192': 'AES192-GCM-16',
            'AES_GCM_16_256': 'AES256-GCM-16',
            'AES_GCM_8_128': 'AES128-GCM-8',
            'AES_GCM_8_256': 'AES256-GCM-8',
            'AES_GCM_12_128': 'AES128-GCM-12',
            'AES_GCM_12_256': 'AES256-GCM-12',
            'AES_CCM_16_128': 'AES128-CCM-16',
            'AES_CCM_16_256': 'AES256-CCM-16',
            'AES_CTR_128': 'AES128-CTR',
            'AES_CTR_192': 'AES192-CTR',
            'AES_CTR_256': 'AES256-CTR',
            '3DES_CBC': '3DES',
            'DES_CBC': 'DES',
            'CAMELLIA_CBC_128': 'CAMELLIA128',
            'CAMELLIA_CBC_256': 'CAMELLIA256',
            'CHACHA20_POLY1305': 'CHACHA20-POLY1305',
            'BLOWFISH_CBC': 'BLOWFISH',
            'CAST5_CBC': 'CAST5'
        }
        
        self.hash_mapping = {
            'HMAC_MD5': 'MD5',
            'HMAC_MD5_96': 'MD5',
            'HMAC_SHA1': 'SHA1',
            'HMAC_SHA1_96': 'SHA1',
            'HMAC_SHA2_256': 'SHA2-256',
            'HMAC_SHA2_256_128': 'SHA2-256',
            'HMAC_SHA2_384': 'SHA2-384',
            'HMAC_SHA2_384_192': 'SHA2-384',
            'HMAC_SHA2_512': 'SHA2-512',
            'HMAC_SHA2_512_256': 'SHA2-512',
            'HMAC_SHA3_224': 'SHA3-224',
            'HMAC_SHA3_256': 'SHA3-256',
            'HMAC_SHA3_384': 'SHA3-384',
            'HMAC_SHA3_512': 'SHA3-512',
            'AES_GMAC_128': 'GMAC-128',
            'AES_GMAC_192': 'GMAC-192',
            'AES_GMAC_256': 'GMAC-256',
            'POLY1305': 'POLY1305'
        }
        
        self.prf_mapping = {
            'PRF_HMAC_MD5': 'MD5',
            'PRF_HMAC_SHA1': 'SHA1',
            'PRF_HMAC_SHA2_256': 'SHA2-256',
            'PRF_HMAC_SHA2_384': 'SHA2-384',
            'PRF_HMAC_SHA2_512': 'SHA2-512',
            'PRF_AES128_CMAC': 'AES128-CMAC',
            'PRF_AES128_XCBC': 'AES128-XCBC',
            'PRF_HMAC_SHA3_224': 'SHA3-224',
            'PRF_HMAC_SHA3_256': 'SHA3-256',
            'PRF_HMAC_SHA3_384': 'SHA3-384',
            'PRF_HMAC_SHA3_512': 'SHA3-512'
        }

    def parse_ike_proposal(self, proposal):
        """
        Parse an IKE or ESP proposal string into a structured format.

        Args:
            proposal (str): The proposal string, e.g., "IKE:AES_CBC_256/HMAC_SHA2_256/PRF_HMAC_SHA2_256/MODP_2048"
            
        Returns:
            dict: A dictionary with keys 'encryption', 'hash', 'prf', and 'dh_group'
        """

        # Split the proposal into components based on '/'
        components = proposal.split('/')
        
        result = {
            'encryption': [],
            'hash': [],
            'prf': [],
            'dh_group': []
        }
        
        
        is_ike = proposal.startswith('IKE:')
        is_esp = proposal.startswith('ESP:')
        

        # Remove IKE or ESP prefix if present for easier parsing later
        if is_ike or is_esp:
            components[0] = components[0].replace('IKE:', '').replace('ESP:', '')
        

        enc_components = []
        hash_components = []
        prf_components = []
        dh_components = []

        
        # Determine the current section based on the first component
        current_section = 'enc'
        for component in components:
            if component in self.enc_mapping:
                if current_section != 'enc':
                    current_section = 'enc'
                enc_components.append(component)
            elif component in self.hash_mapping:
                if current_section != 'hash':
                    current_section = 'hash'
                hash_components.append(component)
            elif is_ike and 'PRF_' in component:
                if current_section != 'prf':
                    current_section = 'prf'
                prf_components.append(component)
            elif component in self.dh_mapping:
                if current_section != 'dh':
                    current_section = 'dh'
                dh_components.append(component)
            elif component == 'NO_EXT_SEQ':
                continue  # Skip NO_EXT_SEQ as it’s not relevant to crypto algorithms
        

        # Map encryption components
        for enc in enc_components:
            mapped_enc = self.enc_mapping.get(enc, 'Unknown')
            if mapped_enc != 'Unknown' and mapped_enc not in result['encryption']:
                result['encryption'].append(mapped_enc)
        
        # Map hash components (skip for AEAD ciphers like AES-GCM)
        if not any(enc.startswith('AES_GCM') or enc.startswith('AES_CCM') or enc == 'CHACHA20_POLY1305' for enc in enc_components):
            for hash_val in hash_components:
                mapped_hash = self.hash_mapping.get(hash_val, 'Unknown')
                if mapped_hash != 'Unknown' and mapped_hash not in result['hash']:
                    result['hash'].append(mapped_hash)
        else:
            result['hash'] = ['None']

        
        # Map PRF components
        for prf in prf_components:
            mapped_prf = self.prf_mapping.get(prf, 'Unknown')
            if mapped_prf == "Unknown":
                result['prf'].append(mapped_prf)
            if mapped_prf != 'Unknown' and mapped_prf not in result['prf']:
                result['prf'].append(mapped_prf)


        # Map DH group components
        for dh in dh_components:
            mapped_dh = self.dh_mapping.get(dh)
            if mapped_dh != 'None' and mapped_dh not in result['dh_group']:
                result['dh_group'].append(mapped_dh)

        
        # Handle ESP case (no PRF for ESP proposals)
        if is_esp:
            result['prf'] = ['None']
        
        if not result['encryption']:
            result['encryption'] = ['Unknown']

        if not result['hash']:
            result['hash'] = ['Unknown']

        return result

    def process_proposals(self, proposal_list):
        """
        Process a list of IKE or ESP proposals, concatenating encryption, hash, PRF (for IKE only), 
        and DH group values, and indicate whether PFS is enabled for ESP proposals only.
        
        Args:
            proposal_list (str): Comma-separated string of IKE or ESP proposals
        
        Returns:
            str: Formatted string with concatenated encryption, hash, PRF (for IKE), DH groups, and PFS status (for ESP)
        """
        proposal_list = proposal_list.replace(',', ', ')
        proposals = proposal_list.strip().split(', ')

        
        # Collect unique encryption, hash, PRF, and DH groups
        enc_set = set()
        hash_set = set()
        prf_set = set()
        dh_set = set()


        # Parse each proposal and update the sets for later sorting and formatting
        for proposal in proposals:
            parsed = self.parse_ike_proposal(proposal.strip())
            enc_set.update(parsed['encryption'])
            if parsed['hash'] != ['None']:
                hash_set.update(parsed['hash'])
            if parsed['prf'] != ['None']:
                prf_set.update(parsed['prf'])
            if parsed['dh_group'] != ['None']:
                dh_set.update(parsed['dh_group'])

        
        # Convert sets to sorted lists
        enc_list = sorted(list(enc_set))
        hash_list = sorted(list(hash_set))
        prf_list = sorted(list(prf_set))
        dh_list = sorted(list(dh_set), key=lambda x: int(x))

        
        # Determine PFS status for ESP proposals only
        is_ike = any(proposal.startswith('IKE:') for proposal in proposals)
        pfs_status = "PFS: Enabled" if dh_set and not is_ike else "PFS: None"

        
        # Format output as a single concatenated string
        enc_part = f"Encryption: {', '.join(enc_list)}" if enc_list else "Encryption: None"
        hash_part = f"Hash: {', '.join(hash_list)}" if hash_list else "Hash: None"
        dh_part = f"DH Group(s): {', '.join(dh_list)}" if dh_list else "DH Group(s): None"
        prf_part = f"PRF: {', '.join(prf_list)}" if prf_list else "PRF: None"

        
        # Return formatted string based on whether it's an IKE or ESP proposal
        if is_ike:
            return f"{enc_part} {hash_part} {prf_part} {dh_part}"
        else:
            return f"{enc_part} {hash_part} {dh_part} {pfs_status}"
            
# Example usage
if __name__ == "__main__":
    parser = ProposalParser()

    #IKEV1 PROPOSALS AWS DEFAULT
    ikev1_default_proposals = """IKE:AES_CBC_128/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_1024, IKE:AES_CBC_128/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_2048, IKE:AES_CBC_128/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_3072, IKE:AES_CBC_128/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_4096, IKE:AES_CBC_128/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_6144, IKE:AES_CBC_128/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_8192, IKE:AES_CBC_128/HMAC_SHA1_96/PRF_HMAC_SHA1/ECP_256, IKE:AES_CBC_128/HMAC_SHA1_96/PRF_HMAC_SHA1/ECP_384, IKE:AES_CBC_128/HMAC_SHA1_96/PRF_HMAC_SHA1/ECP_521, IKE:AES_CBC_128/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_1024_160, IKE:AES_CBC_128/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_2048_224, IKE:AES_CBC_128/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_2048_256, IKE:AES_CBC_128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_1024, IKE:AES_CBC_128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_2048, IKE:AES_CBC_128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_3072, IKE:AES_CBC_128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_4096, IKE:AES_CBC_128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_6144, IKE:AES_CBC_128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_8192, IKE:AES_CBC_128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/ECP_256, IKE:AES_CBC_128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/ECP_384, IKE:AES_CBC_128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/ECP_521, IKE:AES_CBC_128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_1024_160, IKE:AES_CBC_128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_2048_224, IKE:AES_CBC_128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_2048_256, IKE:AES_CBC_128/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_1024, IKE:AES_CBC_128/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_2048, IKE:AES_CBC_128/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_3072, IKE:AES_CBC_128/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_4096, IKE:AES_CBC_128/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_6144, IKE:AES_CBC_128/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_8192, IKE:AES_CBC_128/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/ECP_256, IKE:AES_CBC_128/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/ECP_384, IKE:AES_CBC_128/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/ECP_521, IKE:AES_CBC_128/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_1024_160, IKE:AES_CBC_128/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_2048_224, IKE:AES_CBC_128/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_2048_256, IKE:AES_CBC_128/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/MODP_1024, IKE:AES_CBC_128/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/MODP_2048, IKE:AES_CBC_128/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/MODP_3072, IKE:AES_CBC_128/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/MODP_4096, IKE:AES_CBC_128/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/MODP_6144, IKE:AES_CBC_128/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/MODP_8192, IKE:AES_CBC_128/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/ECP_256, IKE:AES_CBC_128/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/ECP_384, IKE:AES_CBC_128/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/ECP_521, IKE:AES_CBC_128/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/MODP_1024_160, IKE:AES_CBC_128/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/MODP_2048_224, IKE:AES_CBC_128/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/MODP_2048_256, IKE:AES_CBC_256/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_1024, IKE:AES_CBC_256/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_2048, IKE:AES_CBC_256/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_3072, IKE:AES_CBC_256/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_4096, IKE:AES_CBC_256/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_6144, IKE:AES_CBC_256/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_8192, IKE:AES_CBC_256/HMAC_SHA1_96/PRF_HMAC_SHA1/ECP_256, IKE:AES_CBC_256/HMAC_SHA1_96/PRF_HMAC_SHA1/ECP_384, IKE:AES_CBC_256/HMAC_SHA1_96/PRF_HMAC_SHA1/ECP_521, IKE:AES_CBC_256/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_1024_160, IKE:AES_CBC_256/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_2048_224, IKE:AES_CBC_256/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_2048_256, IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_1024, IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_2048, IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_3072, IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_4096, IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_6144, IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_8192, IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/ECP_256, IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/ECP_384, IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/ECP_521, IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_1024_160, IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_2048_224, IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_2048_256, IKE:AES_CBC_256/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_1024, IKE:AES_CBC_256/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_2048, IKE:AES_CBC_256/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_3072, IKE:AES_CBC_256/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_4096, IKE:AES_CBC_256/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_6144, IKE:AES_CBC_256/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_8192, IKE:AES_CBC_256/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/ECP_256, IKE:AES_CBC_256/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/ECP_384, IKE:AES_CBC_256/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/ECP_521, IKE:AES_CBC_256/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_1024_160, IKE:AES_CBC_256/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_2048_224, IKE:AES_CBC_256/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_2048_256, IKE:AES_CBC_256/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/MODP_1024, IKE:AES_CBC_256/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/MODP_2048, IKE:AES_CBC_256/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/MODP_3072, IKE:AES_CBC_256/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/MODP_4096, IKE:AES_CBC_256/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/MODP_6144, IKE:AES_CBC_256/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/MODP_8192, IKE:AES_CBC_256/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/ECP_256, IKE:AES_CBC_256/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/ECP_384, IKE:AES_CBC_256/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/ECP_521, IKE:AES_CBC_256/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/MODP_1024_160, IKE:AES_CBC_256/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/MODP_2048_224, IKE:AES_CBC_256/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/MODP_2048_256"""

    #ESP Phase 2 proposals example AWS DEFAULT
    ikev1_default_esp_proposals = """ESP:AES_CBC_128/HMAC_SHA1_96/MODP_1024/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA1_96/MODP_1536/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA1_96/MODP_2048/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA1_96/MODP_3072/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA1_96/MODP_4096/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA1_96/MODP_6144/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA1_96/MODP_8192/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA1_96/ECP_256/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA1_96/ECP_384/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA1_96/ECP_521/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA1_96/MODP_1024_160/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA1_96/MODP_2048_224/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA1_96/MODP_2048_256/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_256_128/MODP_1024/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_256_128/MODP_1536/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_256_128/MODP_2048/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_256_128/MODP_3072/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_256_128/MODP_4096/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_256_128/MODP_6144/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_256_128/MODP_8192/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_256_128/ECP_256/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_256_128/ECP_384/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_256_128/ECP_521/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_256_128/MODP_1024_160/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_256_128/MODP_2048_224/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_256_128/MODP_2048_256/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_384_192/MODP_1024/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_384_192/MODP_1536/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_384_192/MODP_2048/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_384_192/MODP_3072/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_384_192/MODP_4096/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_384_192/MODP_6144/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_384_192/MODP_8192/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_384_192/ECP_256/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_384_192/ECP_384/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_384_192/ECP_521/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_384_192/MODP_1024_160/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_384_192/MODP_2048_224/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_384_192/MODP_2048_256/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_512_256/MODP_1024/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_512_256/MODP_1536/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_512_256/MODP_2048/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_512_256/MODP_3072/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_512_256/MODP_4096/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_512_256/MODP_6144/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_512_256/MODP_8192/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_512_256/ECP_256/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_512_256/ECP_384/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_512_256/ECP_521/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_512_256/MODP_1024_160/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_512_256/MODP_2048_224/NO_EXT_SEQ, ESP:AES_CBC_128/HMAC_SHA2_512_256/MODP_2048_256/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA1_96/MODP_1024/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA1_96/MODP_1536/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA1_96/MODP_2048/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA1_96/MODP_3072/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA1_96/MODP_4096/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA1_96/MODP_6144/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA1_96/MODP_8192/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA1_96/ECP_256/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA1_96/ECP_384/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA1_96/ECP_521/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA1_96/MODP_1024_160/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA1_96/MODP_2048_224/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA1_96/MODP_2048_256/NO_EXT_SEQ,ESP:AES_CBC_256/HMAC_SHA2_256_128/MODP_1024/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_256_128/MODP_1536/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_256_128/MODP_2048/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_256_128/MODP_3072/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_256_128/MODP_4096/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_256_128/MODP_6144/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_256_128/MODP_8192/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_256_128/ECP_256/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_256_128/ECP_384/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_256_128/ECP_521/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_256_128/MODP_1024_160/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_256_128/MODP_2048_224/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_256_128/MODP_2048_256/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_384_192/MODP_1024/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_384_192/MODP_1536/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_384_192/MODP_2048/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_384_192/MODP_3072/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_384_192/MODP_4096/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_384_192/MODP_6144/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_384_192/MODP_8192/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_384_192/ECP_256/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_384_192/ECP_384/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_384_192/ECP_521/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_384_192/MODP_1024_160/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_384_192/MODP_2048_224/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_384_192/MODP_2048_256/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_512_256/MODP_1024/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_512_256/MODP_1536/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_512_256/MODP_2048/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_512_256/MODP_3072/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_512_256/MODP_4096/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_512_256/MODP_6144/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_512_256/MODP_8192/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_512_256/ECP_256/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_512_256/ECP_384/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_512_256/ECP_521/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_512_256/MODP_1024_160/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_512_256/MODP_2048_224/NO_EXT_SEQ, ESP:AES_CBC_256/HMAC_SHA2_512_256/MODP_2048_256/NO_EXT_SEQ, ESP:AES_GCM_16_128/MODP_1024/NO_EXT_SEQ, ESP:AES_GCM_16_128/MODP_1536/NO_EXT_SEQ, ESP:AES_GCM_16_128/MODP_2048/NO_EXT_SEQ, ESP:AES_GCM_16_128/MODP_3072/NO_EXT_SEQ, ESP:AES_GCM_16_128/MODP_4096/NO_EXT_SEQ, ESP:AES_GCM_16_128/MODP_6144/NO_EXT_SEQ, ESP:AES_GCM_16_128/MODP_8192/NO_EXT_SEQ, ESP:AES_GCM_16_128/ECP_256/NO_EXT_SEQ, ESP:AES_GCM_16_128/ECP_384/NO_EXT_SEQ, ESP:AES_GCM_16_128/ECP_521/NO_EXT_SEQ, ESP:AES_GCM_16_128/MODP_1024_160/NO_EXT_SEQ, ESP:AES_GCM_16_128/MODP_2048_224/NO_EXT_SEQ, ESP:AES_GCM_16_128/MODP_2048_256/NO_EXT_SEQ, ESP:AES_GCM_16_256/MODP_1024/NO_EXT_SEQ, ESP:AES_GCM_16_256/MODP_1536/NO_EXT_SEQ, ESP:AES_GCM_16_256/MODP_2048/NO_EXT_SEQ, ESP:AES_GCM_16_256/MODP_3072/NO_EXT_SEQ, ESP:AES_GCM_16_256/MODP_4096/NO_EXT_SEQ, ESP:AES_GCM_16_256/MODP_6144/NO_EXT_SEQ, ESP:AES_GCM_16_256/MODP_8192/NO_EXT_SEQ, ESP:AES_GCM_16_256/ECP_256/NO_EXT_SEQ, ESP:AES_GCM_16_256/ECP_384/NO_EXT_SEQ, ESP:AES_GCM_16_256/ECP_521/NO_EXT_SEQ, ESP:AES_GCM_16_256/MODP_1024_160/NO_EXT_SEQ, ESP:AES_GCM_16_256/MODP_2048_224/NO_EXT_SEQ, ESP:AES_GCM_16_256/MODP_2048_256/NO_EXT_SEQ"""

    #IKEV2 PROPOSALS AWS DEFAULT
    ikev2_default_proposals = """IKE:AES_CBC_128/AES_CBC_256/HMAC_SHA1_96/HMAC_SHA2_256_128/HMAC_SHA2_384_192/HMAC_SHA2_512_256/PRF_HMAC_SHA1/PRF_HMAC_SHA2_256/PRF_HMAC_SHA2_384/PRF_HMAC_SHA2_512/MODP_1024/MODP_2048/MODP_3072/MODP_4096/MODP_6144/MODP_8192/ECP_256/ECP_384/ECP_521/MODP_1024_160/MODP_2048_224/MODP_2048_256, IKE:AES_GCM_16_128/AES_GCM_16_256/PRF_HMAC_SHA1/PRF_HMAC_SHA2_256/PRF_HMAC_SHA2_384/PRF_HMAC_SHA2_512/MODP_1024/MODP_2048/MODP_3072/MODP_4096/MODP_6144/MODP_8192/ECP_256/ECP_384/ECP_521/MODP_1024_160/MODP_2048_224/MODP_2048_256"""

    #ESP PROPOSALS Phase 2 AWS DEFAULT 
    #ikev2_default_esp_proposals = """IKE:AES_CBC_256/HMAC_SHA1_96/PRF_INVALID/MODP_2048"""
    ikev2_default_esp_proposals = """IKE:AES_CBC_128/AES_CBC_256/HMAC_SHA1_96/HMAC_SHA2_256_128/HMAC_SHA2_384_192/HMAC_SHA2_512_256/PRF_HMAC_SHA1/PRF_HMAC_SHA2_256/PRF_HMAC_SHA2_384/PRF_HMAC_SHA2_512/MODP_1024/MODP_2048/MODP_3072/MODP_4096/MODP_6144/MODP_8192/ECP_256/ECP_384/ECP_521/MODP_1024_160/MODP_2048_224/MODP_2048_256, IKE:AES_GCM_16_128/AES_GCM_16_256/PRF_HMAC_SHA1/PRF_HMAC_SHA2_256/PRF_HMAC_SHA2_384/PRF_HMAC_SHA2_512/MODP_1024/MODP_2048/MODP_3072/MODP_4096/MODP_6144/MODP_8192/ECP_256/ECP_384/ECP_521/MODP_1024_160/MODP_2048_224/MODP_2048_256"""
    #ikev2_default_esp_proposals = """IKE:AES_CBC_256/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_2048"""
    #proposal = "IKE:AES_CBC_128/AES_CBC_256/HMAC_SHA1_96/HMAC_SHA2_256_128/HMAC_SHA2_384_192/HMAC_SHA2_512_256/PRF_HMAC_SHA1/PRF_HMAC_SHA2_256/PRF_HMAC_SHA2_384/PRF_HMAC_SHA2_512/MODP_1024/MODP_2048/MODP_3072/MODP_4096/MODP_6144/MODP_8192/ECP_256/ECP_384/ECP_521/MODP_1024_160/MODP_2048_224/MODP_2048_256, IKE:AES_GCM_16_128/AES_GCM_16_256/PRF_HMAC_SHA1/PRF_HMAC_SHA2_256/PRF_HMAC_SHA2_384/PRF_HMAC_SHA2_512/MODP_1024/MODP_2048/MODP_3072/MODP_4096/MODP_6144/MODP_8192/ECP_256/ECP_384/ECP_521/MODP_1024_160/MODP_2048_224/MODP_2048_256"
    proposal = """
    
    testing = """IKE:AES_CBC_128/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/MODP_2048,IKE:AES_CBC_128/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/MODP_3072"""
    # print(f'IKEv1 AWS DEFAULT PROPOSALS\n{parser.process_proposals(ikev1_default_proposals)}')
    # print(f'\n\nIKEv2 AWS DEFAULT PROPOSALS\n{parser.process_proposals(ikev2_default_proposals)}')
    # print(f'\n\nIKEv1 ESP PROPOSALS AWS DEFAULT\n{parser.process_proposals(ikev1_default_esp_proposals)}')
    print(f'\n\nIKEv2 ESP PROPOSALS AWS DEFAULT\n{parser.process_proposals(proposal)}')