"""
DecentraBrowser - A Decentralized Browser System
================================================

This implementation includes a complete Python-based solution for the decentralized browser
system with custom DDNS, encryption, P2P messaging, and file sharing capabilities.

Project Structure:
-----------------
/decentrabrowser/
  ├── central_server/       # Central registration server
  ├── client/               # Client browser application
  │   ├── gui/              # GUI components
  │   ├── network/          # Network utilities
  │   └── security/         # Security and encryption
  ├── shared/               # Shared utilities and protocols
  ├── docs/                 # Documentation
  └── tests/                # Test suite

Installation Instructions:
------------------------
1. Ensure Python 3.8+ is installed
2. Install dependencies: pip install -r requirements.txt
3. Run central server: python -m decentrabrowser.central_server.app
4. Run client: python -m decentrabrowser.client.app

"""

#####################################################################
# requirements.txt
#####################################################################

"""
# Core dependencies
cryptography>=3.4.0
PyQt5>=5.15.4
flask>=2.0.1
requests>=2.25.1
pyOpenSSL>=20.0.1
pycryptodome>=3.10.1
click>=8.0.0
python-dotenv>=0.19.0
tinydb>=4.5.0
marshmallow>=3.13.0
aiohttp>=3.7.4
sqlalchemy>=1.4.23
pynacl>=1.4.0
qrcode>=7.3.1
pillow>=8.3.1
PyQtWebEngine>=5.15.4
colorama>=0.4.4
"""

#####################################################################
# /decentrabrowser/shared/constants.py
#####################################################################

"""
Shared constants for the DecentraBrowser system.
"""

# Protocol constants
DEFAULT_PORT = 8844
DEFAULT_CENTRAL_SERVER_PORT = 8800
DEFAULT_REGISTRY_REFRESH = 300  # seconds

# Security constants
RSA_KEY_SIZE = 4096
AES_KEY_SIZE = 256
PFS_CURVE = 'secp384r1'
CERT_EXPIRY_DAYS = 365

# Path constants
DEFAULT_CERTS_DIR = 'certs'
DEFAULT_DOWNLOADS_DIR = 'downloads'
DEFAULT_DDNS_FILE = 'ddns.json'
DEFAULT_CONFIG_FILE = 'config.json'

# Command constants
CMD_SOURCES = '/sources'
CMD_DOWNLOAD = '/download'
CMD_P2P = '/p2p'

# Protocol format constants
DOMAIN_FORMAT = "{prefix}.{main}.{suffix}.domain"

# GUI constants
GUI_TITLE = "DecentraBrowser"
GUI_WIDTH = 1024
GUI_HEIGHT = 768

#####################################################################
# /decentrabrowser/shared/utils.py
#####################################################################

"""
Utility functions shared across components.
"""
import os
import json
import logging
import platform
import random
import string
import socket
import ipaddress
from pathlib import Path
from datetime import datetime, timedelta

def setup_logging(name, level=logging.INFO):
    """Setup logging with a consistent format."""
    logger = logging.getLogger(name)
    logger.setLevel(level)
    
    if not logger.handlers:
        handler = logging.StreamHandler()
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
    
    return logger

def generate_random_string(length=16):
    """Generate a random string of fixed length."""
    characters = string.ascii_letters + string.digits
    return ''.join(random.choice(characters) for _ in range(length))

def ensure_directory_exists(path):
    """Ensure that a directory exists, creating it if necessary."""
    os.makedirs(path, exist_ok=True)
    return path

def get_data_dir():
    """Get the appropriate data directory for the platform."""
    home = Path.home()
    
    if platform.system() == "Windows":
        return home / "AppData" / "Local" / "DecentraBrowser"
    elif platform.system() == "Darwin":
        return home / "Library" / "Application Support" / "DecentraBrowser"
    else:
        return home / ".decentrabrowser"

def load_json_file(file_path, default=None):
    """Load JSON from a file, returning default if the file doesn't exist."""
    if default is None:
        default = {}
    
    try:
        with open(file_path, 'r') as f:
            return json.load(f)
    except (FileNotFoundError, json.JSONDecodeError):
        return default

def save_json_file(file_path, data):
    """Save data as JSON to a file."""
    directory = os.path.dirname(file_path)
    if directory:
        os.makedirs(directory, exist_ok=True)
        
    with open(file_path, 'w') as f:
        json.dump(data, f, indent=2)

def get_local_ip():
    """Get the local IP address."""
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        # Doesn't need to be reachable
        s.connect(('10.255.255.255', 1))
        ip = s.getsockname()[0]
    except Exception:
        ip = '127.0.0.1'
    finally:
        s.close()
    return ip

def is_valid_domain_format(domain):
    """Check if a domain follows the prefix.main.suffix.domain format."""
    parts = domain.split('.')
    if len(parts) != 4 or parts[3] != 'domain':
        return False
    return all(part for part in parts)  # Ensure no empty parts

def is_valid_ipv4(ip):
    """Check if a string is a valid IPv4 address."""
    try:
        ipaddress.IPv4Address(ip)
        return True
    except ValueError:
        return False

#####################################################################
# /decentrabrowser/shared/security.py
#####################################################################

"""
Security utilities for encryption, key management, and secure communications.
"""
import os
import base64
import hashlib
import time
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.x509 import load_pem_x509_certificate
from cryptography.x509.oid import NameOID
from cryptography import x509
from datetime import datetime, timedelta
import secrets

class SecurityManager:
    """Manager for security operations including encryption, decryption, and cert management."""
    
    def __init__(self, certs_dir):
        """Initialize the security manager with the certificates directory."""
        self.certs_dir = certs_dir
        os.makedirs(certs_dir, exist_ok=True)
    
    def generate_rsa_keypair(self, key_size=4096):
        """Generate a new RSA key pair."""
        private_key = rsa.generate_private_key(
            public_exponent=65537,
            key_size=key_size
        )
        public_key = private_key.public_key()
        
        return private_key, public_key
    
    def save_private_key(self, private_key, filename, password=None):
        """Save a private key to a file, optionally encrypted with a password."""
        encryption = serialization.NoEncryption()
        if password:
            encryption = serialization.BestAvailableEncryption(password.encode())
        
        pem = private_key.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.PKCS8,
            encryption_algorithm=encryption
        )
        
        with open(os.path.join(self.certs_dir, filename), 'wb') as f:
            f.write(pem)
    
    def save_public_key(self, public_key, filename):
        """Save a public key to a file."""
        pem = public_key.public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo
        )
        
        with open(os.path.join(self.certs_dir, filename), 'wb') as f:
            f.write(pem)
    
    def load_private_key(self, filename, password=None):
        """Load a private key from a file."""
        with open(os.path.join(self.certs_dir, filename), 'rb') as f:
            pem_data = f.read()
        
        password_bytes = password.encode() if password else None
        return serialization.load_pem_private_key(pem_data, password=password_bytes)
    
    def load_public_key(self, filename):
        """Load a public key from a file."""
        with open(os.path.join(self.certs_dir, filename), 'rb') as f:
            pem_data = f.read()
        
        return serialization.load_pem_public_key(pem_data)
    
    def generate_aes_key(self, key_size=256):
        """Generate a random AES key."""
        # Key size in bytes (256 bits = 32 bytes)
        key_bytes = key_size // 8
        return secrets.token_bytes(key_bytes)
    
    def encrypt_with_rsa(self, public_key, data):
        """Encrypt data using RSA public key."""
        if isinstance(data, str):
            data = data.encode()
        
        ciphertext = public_key.encrypt(
            data,
            padding.OAEP(
                mgf=padding.MGF1(algorithm=hashes.SHA256()),
                algorithm=hashes.SHA256(),
                label=None
            )
        )
        
        return base64.b64encode(ciphertext).decode()
    
    def decrypt_with_rsa(self, private_key, encrypted_data):
        """Decrypt data using RSA private key."""
        if isinstance(encrypted_data, str):
            encrypted_data = base64.b64decode(encrypted_data)
        
        plaintext = private_key.decrypt(
            encrypted_data,
            padding.OAEP(
                mgf=padding.MGF1(algorithm=hashes.SHA256()),
                algorithm=hashes.SHA256(),
                label=None
            )
        )
        
        return plaintext
    
    def encrypt_with_aes(self, key, data, iv=None):
        """Encrypt data using AES."""
        if isinstance(data, str):
            data = data.encode()
        
        if iv is None:
            iv = secrets.token_bytes(16)  # 16 bytes for AES
        
        cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
        encryptor = cipher.encryptor()
        
        # PKCS7 padding
        padder = self._pkcs7_pad(data, 16)
        padded_data = padder
        
        ciphertext = encryptor.update(padded_data) + encryptor.finalize()
        return iv + ciphertext  # Prepend IV to ciphertext
    
    def decrypt_with_aes(self, key, encrypted_data):
        """Decrypt data using AES."""
        iv = encrypted_data[:16]  # First 16 bytes are the IV
        ciphertext = encrypted_data[16:]  # The rest is the ciphertext
        
        cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
        decryptor = cipher.decryptor()
        
        padded_plaintext = decryptor.update(ciphertext) + decryptor.finalize()
        plaintext = self._pkcs7_unpad(padded_plaintext)
        
        return plaintext
    
    def _pkcs7_pad(self, data, block_size):
        """PKCS#7 padding for data."""
        padding_length = block_size - (len(data) % block_size)
        padding = bytes([padding_length] * padding_length)
        return data + padding
    
    def _pkcs7_unpad(self, padded_data):
        """Remove PKCS#7 padding."""
        padding_length = padded_data[-1]
        return padded_data[:-padding_length]
    
    def generate_certificate(self, private_key, domain_name, expiry_days=365):
        """Generate a self-signed certificate for a domain."""
        subject = issuer = x509.Name([
            x509.NameAttribute(NameOID.COMMON_NAME, domain_name),
            x509.NameAttribute(NameOID.ORGANIZATION_NAME, "DecentraBrowser"),
        ])
        
        cert = x509.CertificateBuilder().subject_name(
            subject
        ).issuer_name(
            issuer
        ).public_key(
            private_key.public_key()
        ).serial_number(
            x509.random_serial_number()
        ).not_valid_before(
            datetime.utcnow()
        ).not_valid_after(
            datetime.utcnow() + timedelta(days=expiry_days)
        ).add_extension(
            x509.SubjectAlternativeName([x509.DNSName(domain_name)]),
            critical=False,
        ).sign(private_key, hashes.SHA256())
        
        cert_pem = cert.public_bytes(serialization.Encoding.PEM)
        cert_path = os.path.join(self.certs_dir, f"{domain_name}.cert")
        
        with open(cert_path, 'wb') as f:
            f.write(cert_pem)
        
        return cert_path
    
    def verify_certificate(self, cert_path, domain_name):
        """Verify a certificate's validity and domain name."""
        with open(cert_path, 'rb') as f:
            cert_data = f.read()
        
        cert = x509.load_pem_x509_certificate(cert_data)
        
        # Check expiration
        now = datetime.utcnow()
        if now < cert.not_valid_before or now > cert.not_valid_after:
            return False, "Certificate is expired or not yet valid"
        
        # Check domain
        try:
            ext = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
            domains = ext.value.get_values_for_type(x509.DNSName)
            if domain_name not in domains:
                return False, "Domain name doesn't match certificate"
        except x509.ExtensionNotFound:
            cn = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
            if domain_name != cn:
                return False, "Domain name doesn't match certificate"
        
        return True, "Certificate is valid"
    
    def get_certificate_fingerprint(self, cert_path):
        """Get the SHA-256 fingerprint of a certificate."""
        with open(cert_path, 'rb') as f:
            cert_data = f.read()
        
        cert = x509.load_pem_x509_certificate(cert_data)
        fingerprint = cert.fingerprint(hashes.SHA256())
        return ':'.join(format(b, '02x') for b in fingerprint)

    def pfs_init(self):
        """Initialize Perfect Forward Secrecy session."""
        # Generate ephemeral key pair for this session
        private_key, public_key = self.generate_rsa_keypair(key_size=2048)
        
        # Create a session ID
        session_id = secrets.token_hex(16)
        
        return {
            'session_id': session_id,
            'private_key': private_key,
            'public_key': public_key
        }
    
    def pfs_exchange(self, session, peer_public_key_data):
        """Exchange keys for PFS session."""
        # Load peer's ephemeral public key
        peer_public_key = serialization.load_pem_public_key(peer_public_key_data)
        
        # Generate session key
        shared_secret = secrets.token_bytes(32)  # 256-bit AES key
        
        # Encrypt session key with peer's public key
        encrypted_secret = peer_public_key.encrypt(
            shared_secret,
            padding.OAEP(
                mgf=padding.MGF1(algorithm=hashes.SHA256()),
                algorithm=hashes.SHA256(),
                label=None
            )
        )
        
        # Update session
        session['shared_secret'] = shared_secret
        session['established'] = True
        
        return encrypted_secret
    
    def pfs_receive(self, session, encrypted_secret):
        """Receive and process peer's response in PFS session."""
        # Decrypt the session key using our ephemeral private key
        shared_secret = session['private_key'].decrypt(
            encrypted_secret,
            padding.OAEP(
                mgf=padding.MGF1(algorithm=hashes.SHA256()),
                algorithm=hashes.SHA256(),
                label=None
            )
        )
        
        # Update session
        session['shared_secret'] = shared_secret
        session['established'] = True
        
        return True
    
    def pfs_encrypt(self, session, data):
        """Encrypt data using established PFS session."""
        if not session.get('established'):
            raise ValueError("PFS session not established")
        
        return self.encrypt_with_aes(session['shared_secret'], data)
    
    def pfs_decrypt(self, session, encrypted_data):
        """Decrypt data using established PFS session."""
        if not session.get('established'):
            raise ValueError("PFS session not established")
        
        return self.decrypt_with_aes(session['shared_secret'], encrypted_data)

#####################################################################
# /decentrabrowser/shared/domain.py
#####################################################################

"""
Domain handling utilities for the DecentraBrowser system.
"""
import os
import json
import re
from datetime import datetime
from .constants import DOMAIN_FORMAT

class Domain:
    """Class representing a domain in the DecentraBrowser system."""
    
    def __init__(self, prefix, main, suffix, ip=None, port=None):
        """Initialize a new domain."""
        self.prefix = prefix
        self.main = main
        self.suffix = suffix
        self.ip = ip
        self.port = port
        self.last_seen = datetime.utcnow().isoformat()
        self.favicon = None
        self.banner = None
        self.title = None
        self.file_count = 0
    
    @property
    def full_name(self):
        """Get the full domain name."""
        return DOMAIN_FORMAT.format(
            prefix=self.prefix,
            main=self.main,
            suffix=self.suffix
        )
    
    @property
    def address(self):
        """Get the full address (ip:port) for this domain."""
        if self.ip and self.port:
            return f"{self.ip}:{self.port}"
        return None
    
    @classmethod
    def from_full_name(cls, full_name, ip=None, port=None):
        """Create a Domain instance from a full domain name."""
        parts = full_name.split('.')
        if len(parts) != 4 or parts[3] != 'domain':
            raise ValueError(f"Invalid domain format: {full_name}")
        
        return cls(
            prefix=parts[0],
            main=parts[1],
            suffix=parts[2],
            ip=ip,
            port=port
        )
    
    def to_dict(self):
        """Convert the domain to a dictionary."""
        return {
            'prefix': self.prefix,
            'main': self.main,
            'suffix': self.suffix,
            'full_name': self.full_name,
            'ip': self.ip,
            'port': self.port,
            'address': self.address,
            'last_seen': self.last_seen,
            'favicon': self.favicon,
            'banner': self.banner,
            'title': self.title,
            'file_count': self.file_count
        }
    
    @classmethod
    def from_dict(cls, data):
        """Create a Domain instance from a dictionary."""
        domain = cls(
            prefix=data['prefix'],
            main=data['main'],
            suffix=data['suffix'],
            ip=data.get('ip'),
            port=data.get('port')
        )
        domain.last_seen = data.get('last_seen', datetime.utcnow().isoformat())
        domain.favicon = data.get('favicon')
        domain.banner = data.get('banner')
        domain.title = data.get('title')
        domain.file_count = data.get('file_count', 0)
        return domain
    
    def __str__(self):
        """String representation of the domain."""
        return f"{self.full_name} ({self.address})"


class DomainRegistry:
    """Class for managing domain registrations."""
    
    def __init__(self, registry_file):
        """Initialize the domain registry."""
        self.registry_file = registry_file
        self.domains = {}
        self.load()
    
    def load(self):
        """Load the domain registry from file."""
        try:
            with open(self.registry_file, 'r') as f:
                data = json.load(f)
                for domain_data in data.values():
                    domain = Domain.from_dict(domain_data)
                    self.domains[domain.full_name] = domain
        except (FileNotFoundError, json.JSONDecodeError):
            self.domains = {}
    
    def save(self):
        """Save the domain registry to file."""
        os.makedirs(os.path.dirname(self.registry_file), exist_ok=True)
        with open(self.registry_file, 'w') as f:
            json.dump({d.full_name: d.to_dict() for d in self.domains.values()}, f, indent=2)
    
    def register(self, domain):
        """Register a domain in the registry."""
        if not domain.ip or not domain.port:
            raise ValueError("Domain must have IP and port to register")
        
        domain.last_seen = datetime.utcnow().isoformat()
        self.domains[domain.full_name] = domain
        self.save()
        return True
    
    def unregister(self, domain_name):
        """Remove a domain from the registry."""
        if domain_name in self.domains:
            del self.domains[domain_name]
            self.save()
            return True
        return False
    
    def get_domain(self, domain_name):
        """Get a domain by name."""
        return self.domains.get(domain_name)
    
    def update_domain(self, domain_name, **kwargs):
        """Update domain properties."""
        if domain_name not in self.domains:
            return False
        
        domain = self.domains[domain_name]
        for key, value in kwargs.items():
            if hasattr(domain, key):
                setattr(domain, key, value)
        
        domain.last_seen = datetime.utcnow().isoformat()
        self.save()
        return True
    
    def list_domains(self):
        """List all registered domains."""
        return list(self.domains.values())
    
    def prune_inactive(self, max_age_hours=24):
        """Remove domains that haven't been seen recently."""
        now = datetime.utcnow()
        to_remove = []
        
        for name, domain in self.domains.items():
            last_seen = datetime.fromisoformat(domain.last_seen)
            age = (now - last_seen).total_seconds() / 3600  # hours
            
            if age > max_age_hours:
                to_remove.append(name)
        
        for name in to_remove:
            del self.domains[name]
        
        if to_remove:
            self.save()
        
        return len(to_remove)

#####################################################################
# /decentrabrowser/shared/ddns.py
#####################################################################

"""
DDNS (Dynamic DNS) implementation for the DecentraBrowser system.
"""
import os
import json
from datetime import datetime
from .constants import DEFAULT_DDNS_FILE
from .utils import load_json_file, save_json_file, is_valid_domain_format, is_valid_ipv4

class DDNS:
    """Dynamic DNS manager for resolving domain names to IP addresses."""
    
    def __init__(self, ddns_file=DEFAULT_DDNS_FILE):
        """Initialize the DDNS manager with a file path."""
        self.ddns_file = ddns_file
        self.entries = {}
        self.load()
    
    def load(self):
        """Load DDNS entries from file."""
        self.entries = load_json_file(self.ddns_file, default={})
        
    def save(self):
        """Save DDNS entries to file."""
        save_json_file(self.ddns_file, self.entries)
    
    def add_entry(self, domain, ip, port):
        """Add or update a DDNS entry."""
        if not is_valid_domain_format(domain):
            return False, "Invalid domain format"
        
        if not is_valid_ipv4(ip):
            return False, "Invalid IP address"
        
        try:
            port = int(port)
            if port < 1 or port > 65535:
                return False, "Port must be between 1 and 65535"
        except ValueError:
            return False, "Port must be a number"
        
        self.entries[domain] = {
            'ip': ip,
            'port': port,
            'updated': datetime.utcnow().isoformat()
        }
        
        self.save()
        return True, "Entry added successfully"
    
    def remove_entry(self, domain):
        """Remove a DDNS entry."""
        if domain in self.entries:
            del self.entries[domain]
            self.save()
            return True, "Entry removed successfully"
        return False, "Domain not found"
    
    def resolve(self, domain):
        """Resolve a domain to its IP and port."""
        entry = self.entries.get(domain)
        if not entry:
            return None, None
        
        return entry['ip'], entry['port']
    
    def list_entries(self):
        """List all DDNS entries."""
        return {domain: entry for domain, entry in self.entries.items()}
    
    def has_domain(self, domain):
        """Check if a domain exists in the DDNS file."""
        return domain in self.entries
    
    def update_from_registry(self, domains):
        """Update DDNS entries from a list of Domain objects."""
        updated = 0
        for domain in domains:
            if domain.ip and domain.port:
                result, _ = self.add_entry(domain.full_name, domain.ip, domain.port)
                if result:
                    updated += 1
        
        return updated

#####################################################################
# /decentrabrowser/central_server/app.py
#####################################################################

"""
Central registration server for the DecentraBrowser system.
"""
import os
import json
import time
from flask import Flask, request, jsonify
import threading
from ..shared.constants import DEFAULT_CENTRAL_SERVER_PORT
from ..shared.domain import Domain, DomainRegistry
from ..shared.utils import setup_logging, ensure_directory_exists, get_data_dir

# Setup logger
logger = setup_logging('central_server')

class CentralServer:
    """Central registration server for the DecentraBrowser system."""
    
    def __init__(self, data_dir=None, port=DEFAULT_CENTRAL_SERVER_PORT):
        """Initialize the central server."""
        if data_dir is None:
            data_dir = get_data_dir() / 'central_server'
        
        self.data_dir = ensure_directory_exists(data_dir)
        self.registry_file = os.path.join(self.data_dir, 'registry.json')
        self.port = port
        
        # Initialize domain registry
        self.registry = DomainRegistry(self.registry_file)
        
        # Initialize Flask app
        self.app = Flask(__name__)
        self.setup_routes()
        
        # Background maintenance thread
        self.maintenance_thread = None
        self.running = False
    
    def setup_routes(self):
        """Setup the Flask routes."""
        
        @self.app.route('/api/register', methods=['POST'])
        def register_domain():
            """Register a new domain."""
            data = request.json
            
            if not data or not isinstance(data, dict):
                return jsonify({'success': False, 'error': 'Invalid data format'}), 400
            
            required_fields = ['prefix', 'main', 'suffix', 'ip', 'port']
            if not all(field in data for field in required_fields):
                return jsonify({'success': False, 'error': 'Missing required fields'}), 400
            
            try:
                domain = Domain(
                    prefix=data['prefix'],
                    main=data['main'],
                    suffix=data['suffix'],
                    ip=data['ip'],
                    port=data['port']
                )
                
                # Add optional metadata if provided
                if 'favicon' in data:
                    domain.favicon = data['favicon']
                if 'banner' in data:
                    domain.banner = data['banner']
                if 'title' in data:
                    domain.title = data['title']
                if 'file_count' in data:
                    domain.file_count = data['file_count']
                
                self.registry.register(domain)
                
                return jsonify({
                    'success': True,
                    'domain': domain.full_name,
                    'message': 'Domain registered successfully'
                })
                
            except Exception as e:
                logger.error(f"Error registering domain: {str(e)}")
                return jsonify({'success': False, 'error': str(e)}), 500
        
        @self.app.route('/api/unregister', methods=['POST'])
        def unregister_domain():
            """Unregister a domain."""
            data = request.json
            
            if not data or not isinstance(data, dict) or 'domain' not in data:
                return jsonify({'success': False, 'error': 'Invalid data format'}), 400
            
            domain_name = data['domain']
            result = self.registry.unregister(domain_name)
            
            if result:
                return jsonify({
                    'success': True,
                    'message': f'Domain {domain_name} unregistered successfully'
                })
            else:
                return jsonify({
                    'success': False,
                    'error': f'Domain {domain_name} not found'
                }), 404
        
        @self.app.route('/api/update', methods=['POST'])
        def update_domain():
            """Update domain information."""
            data = request.json
            
            if not data or not isinstance(data, dict) or 'domain' not in data:
                return jsonify({'success': False, 'error': 'Invalid data format'}), 400
            
            domain_name = data['domain']
            update_data = {k: v for k, v in data.items() if k != 'domain'}
            
            result = self.registry.update_domain(domain_name, **update_data)
            
            if result:
                return jsonify({
                    'success': True,
                    'message': f'Domain {domain_name} updated successfully'
                })
            else:
                return jsonify({
                    'success': False,
                    'error': f'Domain {domain_name} not found'
                }), 404
        
        @self.app.route('/api/sources', methods=['GET'])
        def list_domains():
            """List all active domains."""
            domains = self.registry.list_domains()
            return jsonify({
                'success': True,
                'count': len(domains),
                'domains': [d.to_dict() for d in domains]
            })
        
        @self.app.route('/api/resolve/<domain>', methods=['GET'])
        def resolve_domain(domain):
            """Resolve a domain name to its IP and port."""
            domain_obj = self.registry.get_domain(domain)
            
            if domain_obj:
                return jsonify({
                    'success': True,
                    'domain': domain,
                    'ip': domain_obj.ip,
                    'port': domain_obj.port
                })
            else:
                return jsonify({
                    'success': False,
                    'error': f'Domain {domain} not found'
                }), 404
        
        @self.app.route('/health', methods=['GET'])
        def health_check():
            """Health check endpoint."""
            return jsonify({
                'status': 'ok',
                'timestamp': time.time(),
                'domains': len(self.registry.list_domains())
            })
    
    def maintenance_task(self):
        """Background task to prune inactive domains."""
        while self.running:
            try:
                pruned = self.registry.prune_inactive()
                if pruned:
                    logger.info(f"Pruned {pruned} inactive domains")
            except Exception as e:
                logger.error(f"Error in maintenance task: {str(e)}")
            
            # Sleep for 1 hour
            for _ in range(60):  # Check every minute if we should stop
                if not self.running:
                    break
                time.sleep(60)
    
    def start(self):
        """Start the central server."""
        self.running = True
        
        # Start maintenance thread
        self.maintenance_thread = threading.Thread(target=self.maintenance_task)
        self.maintenance_thread.daemon = True
        self.maintenance_thread.start()
        
        # Start Flask app
        self.app.run(host='0.0.0.0', port=self.port)
    
    def stop(self):
        """Stop the central server."""
        self.running = False
        # Flask will be stopped by the caller


def main():
    """Entry point for the central server."""
    import argparse
    
    parser = argparse.ArgumentParser(description='DecentraBrowser Central Server')
    parser.add_argument('--port', type=int, default=DEFAULT_CENTRAL_SERVER_PORT,
                        help=f'Port to listen on (default: {DEFAULT_CENTRAL_SERVER_PORT})')
    parser.add_argument('--data-dir', type=str, default=None,
                        help='Data directory (default: platform-specific user data dir)')
    
    args = parser.parse_args()
    
    # Start the server
    server = CentralServer(data_dir=args.data_dir, port=args.port)
    try:
        logger.info(f"Starting central server on port {args.port}")
        server.start()
    except KeyboardInterrupt:
        logger.info("Shutting down central server")
        server.stop()


if __name__ == '__main__':
    main()

#####################################################################
# /decentrabrowser/client/network/http_server.py
#####################################################################

"""
HTTP server implementation for the client to serve content.
"""
import os
import json
import threading
import socket
from http.server import HTTPServer, BaseHTTPRequestHandler
from http import HTTPStatus
import ssl
import time
from urllib.parse import urlparse, parse_qs, unquote
import base64
import mimetypes

from ...shared.utils import setup_logging, ensure_directory_exists, get_data_dir
from ...shared.security import SecurityManager
from ...shared.domain import Domain
from ...shared.constants import DEFAULT_PORT

# Setup logger
logger = setup_logging('http_server')

class DomainHandler(BaseHTTPRequestHandler):
    """HTTP request handler for domain content."""
    
    def __init__(self, *args, **kwargs):
        # BaseHTTPRequestHandler calls do_METHOD method for each
        # HTTP request it receives.
        BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
    
    def log_message(self, format, *args):
        """Override to use our logger."""
        logger.info(f"{self.address_string()} - {format % args}")
    
    def send_cors_headers(self):
        """Send CORS headers."""
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
        self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
    
    def do_OPTIONS(self):
        """Handle OPTIONS requests for CORS."""
        self.send_response(HTTPStatus.NO_CONTENT)
        self.send_cors_headers()
        self.end_headers()
    
    def do_GET(self):
        """Handle GET requests."""
        try:
            parsed_path = urlparse(self.path)
            path = parsed_path.path
            
            # Check if path is for domain info
            if path == '/' or path == '/info':
                self.send_domain_info()
                return
            
            # Check if path is for a file
            if path.startswith('/file/'):
                file_path = unquote(path[6:])  # Remove '/file/' prefix
                self.send_file(file_path)
                return
            
            # Check if path is for favicon
            if path == '/favicon.ico' and hasattr(self.server, 'domain'):
                self.send_favicon()
                return
            
            # Check if path is for banner
            if path == '/banner' and hasattr(self.server, 'domain'):
                self.send_banner()
                return
            
            # Handle unknown paths
            self.send_error(404, 'Not Found')
            
        except Exception as e:
            logger.error(f"Error handling request: {str(e)}")
            self.send_error(500, f'Internal Server Error: {str(e)}')
    
    def do_POST(self):
        """Handle POST requests."""
        try:
            parsed_path = urlparse(self.path)
            path = parsed_path.path
            
            # Get content length
            content_length = int(self.headers.get('Content-Length', 0))
            body = self.rfile.read(content_length).decode('utf-8')
            
            # Check if path is for PFS initialization
            if path == '/pfs/init':
                self.handle_pfs_init(body)
                return
            
            # Check if path is for PFS key exchange
            if path == '/pfs/exchange':
                self.handle_pfs_exchange(body)
                return
            
            # Check if path is for P2P messaging
            if path == '/p2p/message':
                self.handle_p2p_message(body)
                return
            
            # Handle unknown paths
            self.send_error(404, 'Not Found')
            
        except Exception as e:
            logger.error(f"Error handling request: {str(e)}")
            self.send_error(500, f'Internal Server Error: {str(e)}')
    
    def send_domain_info(self):
        """Send domain information."""
        if not hasattr(self.server, 'domain'):
            self.send_error(404, 'Domain not configured')
            return
        
        domain_info = self.server.domain.to_dict()
        
        # Add file list if available
        if hasattr(self.server, 'content_dir') and os.path.exists(self.server.content_dir):
            file_list = []
            for root, _, files in os.walk(self.server.content_dir):
                for file in files:
                    if file.endswith('.enc'):  # Only include encrypted files
                        file_path = os.path.join(root, file)
                        rel_path = os.path.relpath(file_path, self.server.content_dir)
                        file_size = os.path.getsize(file_path)
                        
                        # Get file type from original name (remove .enc)
                        original_name = file[:-4] if file.endswith('.enc') else file
                        file_type = mimetypes.guess_type(original_name)[0] or 'application/octet-stream'
                        
                        file_list.append({
                            'name': rel_path,
                            'size': file_size,
                            'type': file_type
                        })
            
            domain_info['files'] = file_list
            domain_info['file_count'] = len(file_list)
        
        self.send_response(HTTPStatus.OK)
        self.send_header('Content-Type', 'application/json')
        self.send_cors_headers()
        self.end_headers()
        
        self.wfile.write(json.dumps(domain_info).encode())
    
    def send_file(self, file_path):
        """Send an encrypted file."""
        if not hasattr(self.server, 'content_dir'):
            self.send_error(500, 'Content directory not configured')
            return
        
        # Ensure file_path doesn't contain directory traversal
        norm_path = os.path.normpath(file_path)
        if os.path.isabs(norm_path) or norm_path.startswith('..'):
            self.send_error(403, 'Forbidden')
            return
        
        full_path = os.path.join(self.server.content_dir, norm_path)
        
        if not os.path.exists(full_path):
            self.send_error(404, 'File not found')
            return
        
        # Read and send the file
        try:
            with open(full_path, 'rb') as f:
                file_data = f.read()
            
            file_name = os.path.basename(file_path)
            self.send_response(HTTPStatus.OK)
            self.send_header('Content-Type', 'application/octet-stream')
            self.send_header('Content-Disposition', f'attachment; filename="{file_name}"')
            self.send_header('Content-Length', str(len(file_data)))
            self.send_cors_headers()
            self.end_headers()
            
            self.wfile.write(file_data)
            
        except Exception as e:
            logger.error(f"Error sending file: {str(e)}")
            self.send_error(500, f'Internal Server Error: {str(e)}')
    
    def send_favicon(self):
        """Send the domain favicon."""
        if not hasattr(self.server, 'domain') or not self.server.domain.favicon:
            self.send_error(404, 'Favicon not found')
            return
        
        try:
            # Decode base64 favicon data
            favicon_data = base64.b64decode(self.server.domain.favicon)
            
            self.send_response(HTTPStatus.OK)
            self.send_header('Content-Type', 'image/x-icon')
            self.send_header('Content-Length', str(len(favicon_data)))
            self.send_cors_headers()
            self.end_headers()
            
            self.wfile.write(favicon_data)
            
        except Exception as e:
            logger.error(f"Error sending favicon: {str(e)}")
            self.send_error(500, f'Internal Server Error: {str(e)}')
    
    def send_banner(self):
        """Send the domain banner."""
        if not hasattr(self.server, 'domain') or not self.server.domain.banner:
            self.send_error(404, 'Banner not found')
            return
        
        try:
            # Decode base64 banner data
            banner_data = base64.b64decode(self.server.domain.banner)
            
            self.send_response(HTTPStatus.OK)
            self.send_header('Content-Type', 'image/jpeg')  # Assume JPEG for banners
            self.send_header('Content-Length', str(len(banner_data)))
            self.send_cors_headers()
            self.end_headers()
            
            self.wfile.write(banner_data)
            
        except Exception as e:
            logger.error(f"Error sending banner: {str(e)}")
            self.send_error(500, f'Internal Server Error: {str(e)}')
    
    def handle_pfs_init(self, body):
        """Handle PFS initialization request."""
        if not hasattr(self.server, 'security_manager'):
            self.send_error(500, 'Security manager not configured')
            return
        
        try:
            # Parse request
            data = json.loads(body)
            client_id = data.get('client_id')
            
            if not client_id:
                self.send_error(400, 'Missing client ID')
                return
            
            # Initialize PFS session
            pfs_session = self.server.security_manager.pfs_init()
            
            # Store session for this client
            if not hasattr(self.server, 'pfs_sessions'):
                self.server.pfs_sessions = {}
            
            self.server.pfs_sessions[client_id] = pfs_session
            
            # Send public key to client
            public_key_pem = pfs_session['public_key'].public_bytes(
                encoding=serialization.Encoding.PEM,
                format=serialization.PublicFormat.SubjectPublicKeyInfo
            )
            
            response = {
                'session_id': pfs_session['session_id'],
                'public_key': public_key_pem.decode('utf-8')
            }
            
            self.send_response(HTTPStatus.OK)
            self.send_header('Content-Type', 'application/json')
            self.send_cors_headers()
            self.end_headers()
            
            self.wfile.write(json.dumps(response).encode())
            
        except Exception as e:
            logger.error(f"Error handling PFS init: {str(e)}")
            self.send_error(500, f'Internal Server Error: {str(e)}')
    
    def handle_pfs_exchange(self, body):
        """Handle PFS key exchange request."""
        if not hasattr(self.server, 'security_manager') or not hasattr(self.server, 'pfs_sessions'):
            self.send_error(500, 'Security manager or PFS sessions not configured')
            return
        
        try:
            # Parse request
            data = json.loads(body)
            client_id = data.get('client_id')
            session_id = data.get('session_id')
            encrypted_secret = data.get('encrypted_secret')
            
            if not client_id or not session_id or not encrypted_secret:
                self.send_error(400, 'Missing required fields')
                return
            
            # Get session
            session = self.server.pfs_sessions.get(client_id)
            if not session or session['session_id'] != session_id:
                self.send_error(403, 'Invalid session')
                return
            
            # Decode encrypted secret
            encrypted_secret_bytes = base64.b64decode(encrypted_secret)
            
            # Process exchange
            success = self.server.security_manager.pfs_receive(session, encrypted_secret_bytes)
            
            if not success:
                self.send_error(500, 'Failed to process key exchange')
                return
            
            response = {
                'success': True,
                'message': 'PFS session established'
            }
            
            self.send_response(HTTPStatus.OK)
            self.send_header('Content-Type', 'application/json')
            self.send_cors_headers()
            self.end_headers()
            
            self.wfile.write(json.dumps(response).encode())
            
        except Exception as e:
            logger.error(f"Error handling PFS exchange: {str(e)}")
            self.send_error(500, f'Internal Server Error: {str(e)}')
    
    def handle_p2p_message(self, body):
        """Handle P2P message request."""
        if not hasattr(self.server, 'security_manager') or not hasattr(self.server, 'pfs_sessions'):
            self.send_error(500, 'Security manager or PFS sessions not configured')
            return
        
        try:
            # Parse request
            data = json.loads(body)
            client_id = data.get('client_id')
            session_id = data.get('session_id')
            encrypted_message = data.get('encrypted_message')
            
            if not client_id or not session_id or not encrypted_message:
                self.send_error(400, 'Missing required fields')
                return
            
            # Get session
            session = self.server.pfs_sessions.get(client_id)
            if not session or session['session_id'] != session_id or not session.get('established'):
                self.send_error(403, 'Invalid or not established session')
                return
            
            # Decode encrypted message
            encrypted_message_bytes = base64.b64decode(encrypted_message)
            
            # Decrypt message
            try:
                decrypted_message = self.server.security_manager.pfs_decrypt(
                    session, encrypted_message_bytes
                )
                
                # Process the message
                message_text = decrypted_message.decode('utf-8')
                
                # In a real implementation, you would handle the message here
                # For example, display it to the user or process a command
                
                # Add to message history if available
                if hasattr(self.server, 'message_history'):
                    self.server.message_history.append({
                        'from': client_id,
                        'timestamp': time.time(),
                        'message': message_text
                    })
                
                # Send response
                response = {
                    'success': True,
                    'message': 'Message received'
                }
                
                self.send_response(HTTPStatus.OK)
                self.send_header('Content-Type', 'application/json')
                self.send_cors_headers()
                self.end_headers()
                
                self.wfile.write(json.dumps(response).encode())
                
            except Exception as e:
                logger.error(f"Error decrypting message: {str(e)}")
                self.send_error(500, f'Failed to decrypt message: {str(e)}')
                
        except Exception as e:
            logger.error(f"Error handling P2P message: {str(e)}")
            self.send_error(500, f'Internal Server Error: {str(e)}')


class DomainServer:
    """HTTP server for serving domain content."""
    
    def __init__(self, domain, security_manager, content_dir, port=DEFAULT_PORT):
        """Initialize the domain server."""
        self.domain = domain
        self.security_manager = security_manager
        self.content_dir = ensure_directory_exists(content_dir)
        self.port = port
        
        self.server = None
        self.server_thread = None
        self.running = False
        
        # Message history for P2P communication
        self.message_history = []
        
        # PFS sessions
        self.pfs_sessions = {}
    
    def start(self):
        """Start the HTTP server."""
        if self.running:
            logger.warning("Server already running")
            return
        
        try:
            # Create server
            self.server = HTTPServer(('0.0.0.0', self.port), DomainHandler)
            
            # Attach references to our attributes
            self.server.domain = self.domain
            self.server.security_manager = self.security_manager
            self.server.content_dir = self.content_dir
            self.server.message_history = self.message_history
            self.server.pfs_sessions = self.pfs_sessions
            
            # Start server in a separate thread
            self.server_thread = threading.Thread(target=self.server.serve_forever)
            self.server_thread.daemon = True
            self.server_thread.start()
            
            self.running = True
            logger.info(f"Domain server started on port {self.port}")
            
            return True
            
        except Exception as e:
            logger.error(f"Failed to start domain server: {str(e)}")
            return False
    
    def stop(self):
        """Stop the HTTP server."""
        if not self.running:
            logger.warning("Server not running")
            return
        
        try:
            self.server.shutdown()
            self.server.server_close()
            self.server_thread.join()
            
            self.running = False
            logger.info("Domain server stopped")
            
            return True
            
        except Exception as e:
            logger.error(f"Failed to stop domain server: {str(e)}")
            return False
    
    def is_running(self):
        """Check if the server is running."""
        return self.running

#####################################################################
# /decentrabrowser/client/network/client.py
#####################################################################

"""
Network client for connecting to domains and central server.
"""
import os
import json
import time
import uuid
import base64
import requests
from urllib.parse import urljoin
import threading
from datetime import datetime

from cryptography.hazmat.primitives import serialization

from ...shared.utils import setup_logging, get_local_ip
from ...shared.domain import Domain
from ...shared.ddns import DDNS
from ...shared.constants import (
    DEFAULT_PORT, DEFAULT_CENTRAL_SERVER_PORT, DEFAULT_REGISTRY_REFRESH,
    CMD_SOURCES, CMD_DOWNLOAD, CMD_P2P
)

# Setup logger
logger = setup_logging('network_client')

class NetworkClient:
    """Client for network operations including domain connections and central server communication."""
    
    def __init__(self, security_manager, ddns, central_server_host, central_server_port=DEFAULT_CENTRAL_SERVER_PORT):
        """Initialize the network client."""
        self.security_manager = security_manager
        self.ddns = ddns
        self.central_server_url = f"http://{central_server_host}:{central_server_port}"
        
        # Active PFS sessions
        self.pfs_sessions = {}
        
        # Active downloads
        self.downloads = {}
        
        # Domain registry cache
        self.domain_cache = {}
        self.last_refresh = 0
        
        # For P2P messaging
        self.message_history = {}
        
        # Client ID
        self.client_id = str(uuid.uuid4())
    
    def register_domain(self, domain):
        """Register a domain with the central server."""
        try:
            response = requests.post(
                urljoin(self.central_server_url, 'api/register'),
                json=domain.to_dict(),
                timeout=10
            )
            
            if response.status_code == 200:
                data = response.json()
                if data.get('success'):
                    logger.info(f"Domain {domain.full_name} registered successfully")
                    return True, data.get('message', 'Success')
                else:
                    logger.error(f"Failed to register domain: {data.get('error', 'Unknown error')}")
                    return False, data.get('error', 'Unknown error')
            else:
                logger.error(f"Failed to register domain: HTTP {response.status_code}")
                return False, f"HTTP Error: {response.status_code}"
            
        except requests.RequestException as e:
            logger.error(f"Network error registering domain: {str(e)}")
            return False, f"Network error: {str(e)}"
    
    def unregister_domain(self, domain_name):
        """Unregister a domain from the central server."""
        try:
            response = requests.post(
                urljoin(self.central_server_url, 'api/unregister'),
                json={'domain': domain_name},
                timeout=10
            )
            
            if response.status_code == 200:
                data = response.json()
                if data.get('success'):
                    logger.info(f"Domain {domain_name} unregistered successfully")
                    return True, data.get('message', 'Success')
                else:
                    logger.error(f"Failed to unregister domain: {data.get('error', 'Unknown error')}")
                    return False, data.get('error', 'Unknown error')
            else:
                logger.error(f"Failed to unregister domain: HTTP {response.status_code}")
                return False, f"HTTP Error: {response.status_code}"
            
        except requests.RequestException as e:
            logger.error(f"Network error unregistering domain: {str(e)}")
            return False, f"Network error: {str(e)}"
    
    def update_domain(self, domain_name, **kwargs):
        """Update domain information on the central server."""
        try:
            data = {'domain': domain_name}
            data.update(kwargs)
            
            response = requests.post(
                urljoin(self.central_server_url, 'api/update'),
                json=data,
                timeout=10
            )
            
            if response.status_code == 200:
                data = response.json()
                if data.get('success'):
                    logger.info(f"Domain {domain_name} updated successfully")
                    return True, data.get('message', 'Success')
                else:
                    logger.error(f"Failed to update domain: {data.get('error', 'Unknown error')}")
                    return False, data.get('error', 'Unknown error')
            else:
                logger.error(f"Failed to update domain: HTTP {response.status_code}")
                return False, f"HTTP Error: {response.status_code}"
            
        except requests.RequestException as e:
            logger.error(f"Network error updating domain: {str(e)}")
            return False, f"Network error: {str(e)}"
    
    def get_sources(self, force_refresh=False):
        """Get list of available domains from the central server."""
        # Check if we should use cached data
        now = time.time()
        if not force_refresh and self.domain_cache and (now - self.last_refresh) < DEFAULT_REGISTRY_REFRESH:
            return True, self.domain_cache
        
        try:
            response = requests.get(
                urljoin(self.central_server_url, 'api/sources'),
                timeout=10
            )
            
            if response.status_code == 200:
                data = response.json()
                if data.get('success'):
                    domains = data.get('domains', [])
                    
                    # Convert to Domain objects
                    domain_objects = {}
                    for domain_data in domains:
                        domain = Domain.from_dict(domain_data)
                        domain_objects[domain.full_name] = domain
                    
                    # Update cache
                    self.domain_cache = domain_objects
                    self.last_refresh = now
                    
                    logger.info(f"Retrieved {len(domain_objects)} domains from central server")
                    return True, domain_objects
                else:
                    logger.error(f"Failed to get sources: {data.get('error', 'Unknown error')}")
                    return False, data.get('error', 'Unknown error')
            else:
                logger.error(f"Failed to get sources: HTTP {response.status_code}")
                return False, f"HTTP Error: {response.status_code}"
            
        except requests.RequestException as e:
            logger.error(f"Network error getting sources: {str(e)}")
            return False, f"Network error: {str(e)}"
    
    def resolve_domain(self, domain_name, use_central_server=False):
        """Resolve a domain name to IP and port."""
        # First try local DDNS
        ip, port = self.ddns.resolve(domain_name)
        
        if ip and port:
            return True, (ip, port)
        
        # If not found and use_central_server is True, try central server
        if use_central_server:
            try:
                response = requests.get(
                    urljoin(self.central_server_url, f'api/resolve/{domain_name}'),
                    timeout=10
                )
                
                if response.status_code == 200:
                    data = response.json()
                    if data.get('success'):
                        ip = data.get('ip')
                        port = data.get('port')
                        
                        if ip and port:
                            # Add to local DDNS
                            self.ddns.add_entry(domain_name, ip, port)
                            
                            return True, (ip, port)
                        else:
                            logger.error(f"Invalid IP or port from central server")
                            return False, "Invalid IP or port from central server"
                    else:
                        logger.error(f"Failed to resolve domain: {data.get('error', 'Unknown error')}")
                        return False, data.get('error', 'Unknown error')
                else:
                    logger.error(f"Failed to resolve domain: HTTP {response.status_code}")
                    return False, f"HTTP Error: {response.status_code}"
                
            except requests.RequestException as e:
                logger.error(f"Network error resolving domain: {str(e)}")
                return False, f"Network error: {str(e)}"
        
        return False, f"Domain {domain_name} not found in local DDNS"
    
    def get_domain_info(self, domain_name):
        """Get information about a domain."""
        # Resolve domain
        success, result = self.resolve_domain(domain_name)
        if not success:
            return False, result
        
        ip, port = result
        
        try:
            response = requests.get(
                f"http://{ip}:{port}/info",
                timeout=10
            )
            
            if response.status_code == 200:
                data = response.json()
                return True, data
            else:
                logger.error(f"Failed to get domain info: HTTP {response.status_code}")
                return False, f"HTTP Error: {response.status_code}"
            
        except requests.RequestException as e:
            logger.error(f"Network error getting domain info: {str(e)}")
            return False, f"Network error: {str(e)}"
    
    def pfs_initiate(self, domain_name):
        """Initiate a PFS session with a domain."""
        # Resolve domain
        success, result = self.resolve_domain(domain_name)
        if not success:
            return False, result
        
        ip, port = result
        
        try:
            # Send PFS init request
            response = requests.post(
                f"http://{ip}:{port}/pfs/init",
                json={'client_id': self.client_id},
                timeout=10
            )
            
            if response.status_code == 200:
                data = response.json()
                session_id = data.get('session_id')
                public_key_pem = data.get('public_key')
                
                if not session_id or not public_key_pem:
                    logger.error("Missing session ID or public key in PFS init response")
                    return False, "Missing session ID or public key in response"
                
                # Load public key
                public_key = serialization.load_pem_public_key(public_key_pem.encode())
                
                # Create session
                session = {
                    'domain': domain_name,
                    'session_id': session_id,
                    'server_public_key': public_key,
                    'established': False
                }
                
                # Store session
                self.pfs_sessions[domain_name] = session
                
                # Generate our key pair and shared secret
                client_session = self.security_manager.pfs_init()
                session.update(client_session)
                
                # Encrypt shared secret with server's public key
                shared_secret = self.security_manager.generate_aes_key()
                encrypted_secret = public_key.encrypt(
                    shared_secret,
                    padding.OAEP(
                        mgf=padding.MGF1(algorithm=hashes.SHA256()),
                        algorithm=hashes.SHA256(),
                        label=None
                    )
                )
                
                # Send encrypted secret
                exchange_response = requests.post(
                    f"http://{ip}:{port}/pfs/exchange",
                    json={
                        'client_id': self.client_id,
                        'session_id': session_id,
                        'encrypted_secret': base64.b64encode(encrypted_secret).decode()
                    },
                    timeout=10
                )
                
                if exchange_response.status_code == 200:
                    exchange_data = exchange_response.json()
                    if exchange_data.get('success'):
                        # Complete session setup
                        session['shared_secret'] = shared_secret
                        session['established'] = True
                        
                        logger.info(f"PFS session established with {domain_name}")
                        return True, session
                    else:
                        logger.error(f"PFS exchange failed: {exchange_data.get('message', 'Unknown error')}")
                        return False, exchange_data.get('message', 'Unknown error')
                else:
                    logger.error(f"PFS exchange failed: HTTP {exchange_response.status_code}")
                    return False, f"HTTP Error in exchange: {exchange_response.status_code}"
                
            else:
                logger.error(f"PFS initiation failed: HTTP {response.status_code}")
                return False, f"HTTP Error: {response.status_code}"
            
        except requests.RequestException as e:
            logger.error(f"Network error in PFS initiation: {str(e)}")
            return False, f"Network error: {str(e)}"
    
    def download_file(self, domain_name, file_path, save_dir):
        """Download a file from a domain."""
        # Ensure domain is in DDNS
        if not self.ddns.has_domain(domain_name):
            return False, f"Domain {domain_name} not in DDNS"
        
        # Initiate or get PFS session
        session = self.pfs_sessions.get(domain_name)
        if not session or not session.get('established'):
            success, result = self.pfs_initiate(domain_name)
            if not success:
                return False, f"Failed to establish PFS session: {result}"
            session = result
        
        # Resolve domain
        success, result = self.resolve_domain(domain_name)
        if not success:
            return False, result
        
        ip, port = result
        
        try:
            # Download file
            url = f"http://{ip}:{port}/file/{file_path}"
            
            # Generate download ID
            download_id = str(uuid.uuid4())
            
            # Create download record
            download = {
                'id': download_id,
                'domain': domain_name,
                'file_path': file_path,
                'save_dir': save_dir,
                'status': 'downloading',
                'progress': 0,
                'start_time': time.time()
            }
            
            self.downloads[download_id] = download
            
            # Start download in a thread
            threading.Thread(
                target=self._download_file_thread,
                args=(download_id, url, session, save_dir, file_path),
                daemon=True
            ).start()
            
            return True, download_id
            
        except Exception as e:
            logger.error(f"Error initiating download: {str(e)}")
            return False, f"Error: {str(e)}"
    
    def _download_file_thread(self, download_id, url, session, save_dir, file_path):
        """Background thread for downloading a file."""
        download = self.downloads.get(download_id)
        if not download:
            return
        
        try:
            # Download file
            response = requests.get(url, stream=True, timeout=60)
            
            if response.status_code != 200:
                download['status'] = 'failed'
                download['error'] = f"HTTP Error: {response.status_code}"
                logger.error(f"Download failed: HTTP {response.status_code}")
                return
            
            # Get file size
            file_size = int(response.headers.get('Content-Length', 0))
            download['total_size'] = file_size
            
            # Create local file path
            file_name = os.path.basename(file_path)
            local_path = os.path.join(save_dir, file_name)
            os.makedirs(os.path.dirname(local_path), exist_ok=True)
            
            # Download and decrypt
            with open(local_path, 'wb') as f:
                downloaded = 0
                for chunk in response.iter_content(chunk_size=8192):
                    if chunk:
                        # Write chunk
                        f.write(chunk)
                        
                        # Update progress
                        downloaded += len(chunk)
                        if file_size > 0:
                            download['progress'] = (downloaded / file_size) * 100
            
            # Decrypt file if it's encrypted
            if file_name.endswith('.enc'):
                decrypted_path = local_path[:-4]  # Remove .enc
                
                with open(local_path, 'rb') as encrypted_file:
                    encrypted_data = encrypted_file.read()
                
                # Decrypt using session key
                decrypted_data = self.security_manager.pfs_decrypt(session, encrypted_data)
                
                with open(decrypted_path, 'wb') as decrypted_file:
                    decrypted_file.write(decrypted_data)
                
                # Remove encrypted file
                os.remove(local_path)
                
                # Update download record
                download['decrypted_path'] = decrypted_path
            
            # Update download record
            download['status'] = 'completed'
            download['end_time'] = time.time()
            download['local_path'] = local_path
            
            logger.info(f"Download completed: {file_path}")
            
        except Exception as e:
            download['status'] = 'failed'
            download['error'] = str(e)
            logger.error(f"Download failed: {str(e)}")
    
    def get_download_status(self, download_id):
        """Get the status of a download."""
        download = self.downloads.get(download_id)
        if not download:
            return False, f"Download ID {download_id} not found"
        
        return True, download
    
    def p2p_send_message(self, domain_name, message, timer=300):
        """Send a P2P message to a domain."""
        # Ensure domain is in DDNS
        if not self.ddns.has_domain(domain_name):
            return False, f"Domain {domain_name} not in DDNS"
        
        # Initiate or get PFS session
        session = self.pfs_sessions.get(domain_name)
        if not session or not session.get('established'):
            success, result = self.pfs_initiate(domain_name)
            if not success:
                return False, f"Failed to establish PFS session: {result}"
            session = result
        
        # Resolve domain
        success, result = self.resolve_domain(domain_name)
        if not success:
            return False, result
        
        ip, port = result
        
        try:
            # Encrypt message
            encrypted_message = self.security_manager.pfs_encrypt(session, message.encode())
            
            # Send message
            response = requests.post(
                f"http://{ip}:{port}/p2p/message",
                json={
                    'client_id': self.client_id,
                    'session_id': session['session_id'],
                    'encrypted_message': base64.b64encode(encrypted_message).decode(),
                    'timer': timer
                },
                timeout=10
            )
            
            if response.status_code == 200:
                data = response.json()
                if data.get('success'):
                    # Add to message history
                    if domain_name not in self.message_history:
                        self.message_history[domain_name] = []
                    
                    self.message_history[domain_name].append({
                        'sent': True,
                        'message': message,
                        'timestamp': time.time()
                    })
                    
                    logger.info(f"Message sent to {domain_name}")
                    return True, "Message sent"
                else:
                    logger.error(f"Failed to send message: {data.get('error', 'Unknown error')}")
                    return False, data.get('error', 'Unknown error')
            else:
                logger.error(f"Failed to send message: HTTP {response.status_code}")
                return False, f"HTTP Error: {response.status_code}"
            
        except requests.RequestException as e:
            logger.error(f"Network error sending message: {str(e)}")
            return False, f"Network error: {str(e)}"
    
    def get_message_history(self, domain_name):
        """Get message history for a domain."""
        if domain_name not in self.message_history:
            return []
        
        return self.message_history[domain_name]
    
    def process_command(self, command):
        """Process a command from the command line interface."""
        parts = command.strip().split()
        if not parts:
            return False, "Empty command"
        
        cmd = parts[0].lower()
        
        # Process /sources command
        if cmd == CMD_SOURCES:
            success, result = self.get_sources(force_refresh=True)
            if success:
                domains = result
                return True, {
                    'command': CMD_SOURCES,
                    'count': len(domains),
                    'domains': [d.to_dict() for d in domains.values()]
                }
            else:
                return False, result
        
        # Process /download command
        elif cmd == CMD_DOWNLOAD and len(parts) >= 3:
            file_path = parts[1]
            domain_name = parts[2]
            save_dir = parts[3] if len(parts) > 3 else os.path.expanduser("~/Downloads")
            
            success, result = self.download_file(domain_name, file_path, save_dir)
            if success:
                download_id = result
                return True, {
                    'command': CMD_DOWNLOAD,
                    'download_id': download_id,
                    'domain': domain_name,
                    'file': file_path
                }
            else:
                return False, result
        
        # Process /p2p command
        elif cmd == CMD_P2P and len(parts) >= 4:
            domain_name = parts[1]
            try:
                timer = int(parts[2])
            except ValueError:
                return False, "Timer must be a number"
            
            message = ' '.join(parts[3:])
            
            success, result = self.p2p_send_message(domain_name, message, timer)
            if success:
                return True, {
                    'command': CMD_P2P,
                    'domain': domain_name,
                    'timer': timer,
                    'message': message
                }
            else:
                return False, result
        
        else:
            return False, f"Unknown command: {command}"

#####################################################################
# /decentrabrowser/client/gui/main_window.py
#####################################################################

"""
Main GUI window for the DecentraBrowser client.
"""
import os
import sys
import time
from datetime import datetime
import threading
import base64
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QLabel, QPushButton, QLineEdit, QTabWidget, QTableWidget,
    QTableWidgetItem, QHeaderView, QFileDialog, QTextEdit, QSplitter,
    QStackedWidget, QListWidget, QListWidgetItem, QToolBar, QAction,
    QStatusBar, QDialog, QFormLayout, QSpinBox, QComboBox, QCheckBox,
    QProgressBar, QMenu, QMessageBox, QFrame
)
from PyQt5.QtGui import QIcon, QPixmap, QFont, QColor, QPalette, QTextCursor
from PyQt5.QtCore import Qt, QSize, QThread, pyqtSignal, QTimer, QUrl
from PyQt5.QtWebEngineWidgets import QWebEngineView

from ...shared.utils import setup_logging
from ...shared.constants import GUI_TITLE, GUI_WIDTH, GUI_HEIGHT
from ..network.client import NetworkClient
from ..network.http_server import DomainServer
from ...shared.domain import Domain

# Setup logger
logger = setup_logging('main_window')

class RefreshDomainsThread(QThread):
    """Background thread for refreshing domains."""
    domains_refreshed = pyqtSignal(bool, object)
    
    def __init__(self, network_client):
        """Initialize the thread."""
        super().__init__()
        self.network_client = network_client
    
    def run(self):
        """Run the thread."""
        success, result = self.network_client.get_sources(force_refresh=True)
        self.domains_refreshed.emit(success, result)


class MainWindow(QMainWindow):
    """Main window for the DecentraBrowser client."""
    
    def __init__(self, network_client, domain_server, security_manager, ddns, content_dir):
        """Initialize the main window."""
        super().__init__()
        
        self.network_client = network_client
        self.domain_server = domain_server
        self.security_manager = security_manager
        self.ddns = ddns
        self.content_dir = content_dir
        
        # Windows state
        self.active_downloads = {}
        self.my_domain = None
        
        self.init_ui()
        self.connect_signals()
        
        # Start domain refresh timer
        self.refresh_timer = QTimer()
        self.refresh_timer.timeout.connect(self.refresh_domains)
        self.refresh_timer.start(30000)  # 30 seconds
        
        # Start download status timer
        self.download_timer = QTimer()
        self.download_timer.timeout.connect(self.update_download_status)
        self.download_timer.start(1000)  # 1 second
        
        # Initial domain refresh
        self.refresh_domains()
    
    def init_ui(self):
        """Initialize the user interface."""
        self.setWindowTitle(GUI_TITLE)
        self.setMinimumSize(GUI_WIDTH, GUI_HEIGHT)
        
        # Setup tabs
        self.tabs = QTabWidget()
        self.tab_domains = QWidget()
        self.tab_files = QWidget()
        self.tab_command = QWidget()
        self.tab_certs = QWidget()
        
        self.tabs.addTab(self.tab_domains, "Domains")
        self.tabs.addTab(self.tab_files, "Files")
        self.tabs.addTab(self.tab_command, "Command")
        self.tabs.addTab(self.tab_certs, "Certificates")
        
        self.setCentralWidget(self.tabs)
        
        # Setup domains tab
        self.setup_domains_tab()
        
        # Setup files tab
        self.setup_files_tab()
        
        # Setup command tab
        self.setup_command_tab()
        
        # Setup certificates tab
        self.setup_certs_tab()
        
        # Setup toolbar
        self.setup_toolbar()
        
        # Setup status bar
        self.setup_status_bar()
        
        # Show the window
        self.show()
    
    def setup_toolbar(self):
        """Setup the toolbar."""
        self.toolbar = QToolBar("Main Toolbar")
        self.toolbar.setIconSize(QSize(24, 24))
        self.addToolBar(self.toolbar)
        
        # Add actions
        self.action_refresh = QAction("Refresh", self)
        self.action_refresh.triggered.connect(self.refresh_domains)
        self.toolbar.addAction(self.action_refresh)
        
        self.action_create_domain = QAction("Create Domain", self)
        self.action_create_domain.triggered.connect(self.create_domain_dialog)
        self.toolbar.addAction(self.action_create_domain)
        
        self.action_settings = QAction("Settings", self)
        self.action_settings.triggered.connect(self.settings_dialog)
        self.toolbar.addAction(self.action_settings)
    
    def setup_status_bar(self):
        """Setup the status bar."""
        self.statusBar = QStatusBar()
        self.setStatusBar(self.statusBar)
        
        # Status indicators
        self.lbl_server_status = QLabel("Server: Offline")
        self.statusBar.addPermanentWidget(self.lbl_server_status)
        
        self.lbl_domains_count = QLabel("Domains: 0")
        self.statusBar.addPermanentWidget(self.lbl_domains_count)
        
        self.lbl_files_count = QLabel("Files: 0")
        self.statusBar.addPermanentWidget(self.lbl_files_count)
        
        # Update server status
        self.update_server_status()
    
    def setup_domains_tab(self):
        """Setup the domains tab."""
        layout = QVBoxLayout()
        
        # Search and refresh bar
        search_layout = QHBoxLayout()
        self.txt_domain_search = QLineEdit()
        self.txt_domain_search.setPlaceholderText("Search domains...")
        self.btn_domain_search = QPushButton("Search")
        self.btn_domain_refresh = QPushButton("Refresh")
        
        search_layout.addWidget(self.txt_domain_search)
        search_layout.addWidget(self.btn_domain_search)
        search_layout.addWidget(self.btn_domain_refresh)
        
        layout.addLayout(search_layout)
        
        # Domains table
        self.tbl_domains = QTableWidget()
        self.tbl_domains.setColumnCount(7)
        self.tbl_domains.setHorizontalHeaderLabels([
            "Domain", "Title", "IP", "Port", "Status", "Files", "Actions"
        ])
        self.tbl_domains.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        
        layout.addWidget(self.tbl_domains)
        
        # Domain details
        details_layout = QVBoxLayout()
        
        self.lbl_domain_details = QLabel("No domain selected")
        details_layout.addWidget(self.lbl_domain_details)
        
        self.domain_details_widget = QWidget()
        self.domain_details_widget.setLayout(details_layout)
        self.domain_details_widget.setMaximumHeight(200)
        
        layout.addWidget(self.domain_details_widget)
        
        self.tab_domains.setLayout(layout)
    
    def setup_files_tab(self):
        """Setup the files tab."""
        layout = QVBoxLayout()
        
        # Files table
        self.tbl_files = QTableWidget()
        self.tbl_files.setColumnCount(6)
        self.tbl_files.setHorizontalHeaderLabels([
            "Name", "Domain", "Size", "Type", "Status", "Actions"
        ])
        self.tbl_files.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        
        layout.addWidget(self.tbl_files)
        
        # Download progress
        progress_layout = QHBoxLayout()
        
        self.lbl_download_status = QLabel("No active downloads")
        self.progress_download = QProgressBar()
        self.progress_download.setVisible(False)
        
        progress_layout.addWidget(self.lbl_download_status)
        progress_layout.addWidget(self.progress_download)
        
        layout.addLayout(progress_layout)
        
        self.tab_files.setLayout(layout)
    
    def setup_command_tab(self):
        """Setup the command tab."""
        layout = QVBoxLayout()
        
        # Command output
        self.txt_command_output = QTextEdit()
        self.txt_command_output.setReadOnly(True)
        self.txt_command_output.setStyleSheet("background-color: #000; color: #0f0; font-family: monospace;")
        layout.addWidget(self.txt_command_output)
        
        # Command input
        input_layout = QHBoxLayout()
        self.lbl_command_prompt = QLabel(">")
        self.txt_command_input = QLineEdit()
        self.txt_command_input.setStyleSheet("font-family: monospace;")
        self.btn_command_send = QPushButton("Send")
        
        input_layout.addWidget(self.lbl_command_prompt)
        input_layout.addWidget(self.txt_command_input)
        input_layout.addWidget(self.btn_command_send)
        
        layout.addLayout(input_layout)
        
        self.tab_command.setLayout(layout)
    
    def setup_certs_tab(self):
        """Setup the certificates tab."""
        layout = QVBoxLayout()
        
        # Certificate management
        cert_layout = QHBoxLayout()
        
        # Left side - list of certificates
        cert_list_layout = QVBoxLayout()
        self.lst_certificates = QListWidget()
        
        cert_list_buttons = QHBoxLayout()
        self.btn_cert_import = QPushButton("Import")
        self.btn_cert_export = QPushButton("Export")
        cert_list_buttons.addWidget(self.btn_cert_import)
        cert_list_buttons.addWidget(self.btn_cert_export)
        
        cert_list_layout.addWidget(QLabel("Certificates"))
        cert_list_layout.addWidget(self.lst_certificates)
        cert_list_layout.addLayout(cert_list_buttons)
        
        # Right side - certificate details
        cert_details_layout = QVBoxLayout()
        self.txt_cert_details = QTextEdit()
        self.txt_cert_details.setReadOnly(True)
        
        cert_details_buttons = QHBoxLayout()
        self.btn_cert_verify = QPushButton("Verify")
        self.btn_cert_delete = QPushButton("Delete")
        cert_details_buttons.addWidget(self.btn_cert_verify)
        cert_details_buttons.addWidget(self.btn_cert_delete)
        
        cert_details_layout.addWidget(QLabel("Certificate Details"))
        cert_details_layout.addWidget(self.txt_cert_details)
        cert_details_layout.addLayout(cert_details_buttons)
        
        # Add to cert layout
        cert_layout.addLayout(cert_list_layout, 1)
        cert_layout.addLayout(cert_details_layout, 2)
        
        layout.addLayout(cert_layout)
        
        self.tab_certs.setLayout(layout)
    
    def connect_signals(self):
        """Connect UI signals to slots."""
        # Domains tab
        self.btn_domain_search.clicked.connect(self.search_domains)
        self.btn_domain_refresh.clicked.connect(self.refresh_domains)
        self.tbl_domains.itemSelectionChanged.connect(self.domain_selected)
        
        # Files tab
        self.tbl_files.itemSelectionChanged.connect(self.file_selected)
        
        # Command tab
        self.btn_command_send.clicked.connect(self.send_command)
        self.txt_command_input.returnPressed.connect(self.send_command)
        
        # Certificates tab
        self.lst_certificates.itemSelectionChanged.connect(self.cert_selected)
        self.btn_cert_import.clicked.connect(self.import_certificate)
        self.btn_cert_export.clicked.connect(self.export_certificate)
        self.btn_cert_verify.clicked.connect(self.verify_certificate)
        self.btn_cert_delete.clicked.connect(self.delete_certificate)
    
    def refresh_domains(self):
        """Refresh the domains list."""
        self.refresh_thread = RefreshDomainsThread(self.network_client)
        self.refresh_thread.domains_refreshed.connect(self.update_domains_table)
        self.refresh_thread.start()
        
        self.statusBar.showMessage("Refreshing domains...", 2000)
    
    def update_domains_table(self, success, result):
        """Update the domains table with refreshed data."""
        if success:
            domains = result
            
            # Clear table
            self.tbl_domains.setRowCount(0)
            
            # Fill table
            for i, domain in enumerate(domains.values()):
                self.tbl_domains.insertRow(i)
                
                domain_item = QTableWidgetItem(domain.full_name)
                title_item = QTableWidgetItem(domain.title or "")
                ip_item = QTableWidgetItem(domain.ip or "")
                port_item = QTableWidgetItem(str(domain.port) if domain.port else "")
                
                # Determine status
                in_ddns = self.ddns.has_domain(domain.full_name)
                status_text = "In DDNS" if in_ddns else "Available"
                status_item = QTableWidgetItem(status_text)
                
                files_item = QTableWidgetItem(str(domain.file_count))
                
                # Add to table
                self.tbl_domains.setItem(i, 0, domain_item)
                self.tbl_domains.setItem(i, 1, title_item)
                self.tbl_domains.setItem(i, 2, ip_item)
                self.tbl_domains.setItem(i, 3, port_item)
                self.tbl_domains.setItem(i, 4, status_item)
                self.tbl_domains.setItem(i, 5, files_item)
                
                # Actions cell
                actions_widget = QWidget()
                actions_layout = QHBoxLayout(actions_widget)
                actions_layout.setContentsMargins(2, 2, 2, 2)
                
                if in_ddns:
                    btn_connect = QPushButton("Connect")
                    btn_connect.setProperty("domain", domain.full_name)
                    btn_connect.clicked.connect(self.connect_to_domain)
                    
                    btn_p2p = QPushButton("P2P")
                    btn_p2p.setProperty("domain", domain.full_name)
                    btn_p2p.clicked.connect(self.p2p_message_dialog)
                    
                    actions_layout.addWidget(btn_connect)
                    actions_layout.addWidget(btn_p2p)
                else:
                    btn_add = QPushButton("Add to DDNS")
                    btn_add.setProperty("domain", domain.full_name)
                    btn_add.clicked.connect(self.add_to_ddns)
                    
                    actions_layout.addWidget(btn_add)
                
                self.tbl_domains.setCellWidget(i, 6, actions_widget)
            
            # Update status bar
            self.lbl_domains_count.setText(f"Domains: {len(domains)}")
            
            # Update DDNS with new domains
            updated = self.ddns.update_from_registry(domains.values())
            if updated:
                self.statusBar.showMessage(f"Added {updated} new domains to DDNS", 3000)
        else:
            error = result
            self.statusBar.showMessage(f"Error refreshing domains: {error}", 5000)
    
    def search_domains(self):
        """Search domains by name or title."""
        search_text = self.txt_domain_search.text().lower()
        
        for i in range(self.tbl_domains.rowCount()):
            domain_name = self.tbl_domains.item(i, 0).text().lower()
            domain_title = self.tbl_domains.item(i, 1).text().lower()
            
            if search_text in domain_name or search_text in domain_title:
                self.tbl_domains.setRowHidden(i, False)
            else:
                self.tbl_domains.setRowHidden(i, True)
    # Continuing from domain_selected in MainWindow class

    def domain_selected(self):
        """Handle domain selection in the table."""
        selected_rows = self.tbl_domains.selectedItems()
        if not selected_rows:
            self.lbl_domain_details.setText("No domain selected")
            return
        
        row = selected_rows[0].row()
        domain_name = self.tbl_domains.item(row, 0).text()
        
        # Get domain details
        success, result = self.network_client.get_domain_info(domain_name)
        if success:
            domain_info = result
            
            # Update details label
            details_text = f"<h3>{domain_info.get('title', domain_name)}</h3>"
            details_text += f"<p><b>Domain:</b> {domain_name}</p>"
            details_text += f"<p><b>Files:</b> {domain_info.get('file_count', 0)}</p>"
            
            if 'files' in domain_info:
                details_text += "<p><b>Available files:</b></p>"
                details_text += "<ul>"
                for file in domain_info['files'][:5]:  # Show first 5 files
                    details_text += f"<li>{file['name']} ({file['size']} bytes)</li>"
                if len(domain_info['files']) > 5:
                    details_text += f"<li>...and {len(domain_info['files']) - 5} more</li>"
                details_text += "</ul>"
            
            self.lbl_domain_details.setText(details_text)
            
            # Update files table if this domain has files
            self.update_files_table(domain_info.get('files', []), domain_name)
        else:
            self.lbl_domain_details.setText(f"Error getting domain details: {result}")
    
    def add_to_ddns(self):
        """Add a domain to the local DDNS."""
        sender = self.sender()
        domain_name = sender.property("domain")
        
        # Find domain in table
        for i in range(self.tbl_domains.rowCount()):
            if self.tbl_domains.item(i, 0).text() == domain_name:
                ip = self.tbl_domains.item(i, 2).text()
                port = self.tbl_domains.item(i, 3).text()
                
                # Add to DDNS
                success, message = self.ddns.add_entry(domain_name, ip, port)
                
                if success:
                    self.statusBar.showMessage(f"Added {domain_name} to DDNS", 3000)
                    
                    # Update row in table
                    status_item = QTableWidgetItem("In DDNS")
                    self.tbl_domains.setItem(i, 4, status_item)
                    
                    # Update actions cell
                    actions_widget = QWidget()
                    actions_layout = QHBoxLayout(actions_widget)
                    actions_layout.setContentsMargins(2, 2, 2, 2)
                    
                    btn_connect = QPushButton("Connect")
                    btn_connect.setProperty("domain", domain_name)
                    btn_connect.clicked.connect(self.connect_to_domain)
                    
                    btn_p2p = QPushButton("P2P")
                    btn_p2p.setProperty("domain", domain_name)
                    btn_p2p.clicked.connect(self.p2p_message_dialog)
                    
                    actions_layout.addWidget(btn_connect)
                    actions_layout.addWidget(btn_p2p)
                    
                    self.tbl_domains.setCellWidget(i, 6, actions_widget)
                else:
                    self.statusBar.showMessage(f"Error adding domain to DDNS: {message}", 5000)
                
                break
    
    def connect_to_domain(self):
        """Connect to a domain."""
        sender = self.sender()
        domain_name = sender.property("domain")
        
        # Initiate PFS session
        self.statusBar.showMessage(f"Connecting to {domain_name}...", 2000)
        
        success, result = self.network_client.pfs_initiate(domain_name)
        if success:
            self.statusBar.showMessage(f"Connected to {domain_name}", 3000)
            
            # Switch to files tab
            self.tabs.setCurrentIndex(1)
            
            # Get domain info to update files table
            success, domain_info = self.network_client.get_domain_info(domain_name)
            if success and 'files' in domain_info:
                self.update_files_table(domain_info['files'], domain_name)
        else:
            self.statusBar.showMessage(f"Error connecting to {domain_name}: {result}", 5000)
    
    def update_files_table(self, files, domain_name):
        """Update the files table with files from a domain."""
        # Clear table
        self.tbl_files.setRowCount(0)
        
        # Fill table
        for i, file in enumerate(files):
            self.tbl_files.insertRow(i)
            
            name_item = QTableWidgetItem(file['name'])
            domain_item = QTableWidgetItem(domain_name)
            size_item = QTableWidgetItem(str(file['size']) + " bytes")
            
            # Determine file type
            file_name = file['name']
            file_ext = os.path.splitext(file_name)[1]
            file_type = file.get('type', "Unknown")
            type_item = QTableWidgetItem(file_type)
            
            status_item = QTableWidgetItem("Available")
            
            # Add to table
            self.tbl_files.setItem(i, 0, name_item)
            self.tbl_files.setItem(i, 1, domain_item)
            self.tbl_files.setItem(i, 2, size_item)
            self.tbl_files.setItem(i, 3, type_item)
            self.tbl_files.setItem(i, 4, status_item)
            
            # Actions cell
            actions_widget = QWidget()
            actions_layout = QHBoxLayout(actions_widget)
            actions_layout.setContentsMargins(2, 2, 2, 2)
            
            btn_download = QPushButton("Download")
            btn_download.setProperty("file", file['name'])
            btn_download.setProperty("domain", domain_name)
            btn_download.clicked.connect(self.download_file)
            
            actions_layout.addWidget(btn_download)
            
            self.tbl_files.setCellWidget(i, 5, actions_widget)
        
        # Update status bar
        self.lbl_files_count.setText(f"Files: {len(files)}")
    
    def file_selected(self):
        """Handle file selection in the table."""
        selected_rows = self.tbl_files.selectedItems()
        if not selected_rows:
            return
        
        row = selected_rows[0].row()
        file_name = self.tbl_files.item(row, 0).text()
        domain_name = self.tbl_files.item(row, 1).text()
        
        # Update status bar
        self.statusBar.showMessage(f"Selected {file_name} from {domain_name}", 3000)
    
    def download_file(self):
        """Download a file from a domain."""
        sender = self.sender()
        file_path = sender.property("file")
        domain_name = sender.property("domain")
        
        # Ask for download location
        save_dir = QFileDialog.getExistingDirectory(self, "Save Location")
        if not save_dir:
            return
        
        # Start download
        self.statusBar.showMessage(f"Downloading {file_path} from {domain_name}...", 2000)
        
        success, result = self.network_client.download_file(domain_name, file_path, save_dir)
        if success:
            download_id = result
            
            # Add to active downloads
            self.active_downloads[download_id] = {
                'file': file_path,
                'domain': domain_name,
                'progress_bar': None
            }
            
            # Show progress bar
            self.progress_download.setVisible(True)
            self.progress_download.setValue(0)
            self.lbl_download_status.setText(f"Downloading {file_path}...")
        else:
            self.statusBar.showMessage(f"Error downloading file: {result}", 5000)
    
    def update_download_status(self):
        """Update the status of active downloads."""
        if not self.active_downloads:
            self.progress_download.setVisible(False)
            self.lbl_download_status.setText("No active downloads")
            return
        
        # Check each download
        completed = []
        for download_id, download in self.active_downloads.items():
            success, status = self.network_client.get_download_status(download_id)
            if success:
                # Update progress
                progress = status.get('progress', 0)
                self.progress_download.setValue(int(progress))
                
                # Check if completed
                if status.get('status') == 'completed':
                    completed.append(download_id)
                    self.statusBar.showMessage(f"Download of {download['file']} completed", 3000)
                
                # Update label
                self.lbl_download_status.setText(
                    f"Downloading {download['file']}: {int(progress)}%"
                )
        
        # Remove completed downloads
        for download_id in completed:
            del self.active_downloads[download_id]
    
    def send_command(self):
        """Send a command to the network client."""
        command = self.txt_command_input.text()
        if not command:
            return
        
        # Add to output
        self.txt_command_output.append(f"> {command}")
        
        # Process command
        success, result = self.network_client.process_command(command)
        
        if success:
            # Format and add result to output
            if isinstance(result, dict):
                if result.get('command') == '/sources':
                    self.txt_command_output.append(f"Found {result['count']} domains")
                    for domain in result['domains'][:5]:  # Show first 5
                        self.txt_command_output.append(f"  {domain['full_name']} - {domain.get('title', '')}")
                    if result['count'] > 5:
                        self.txt_command_output.append(f"  ...and {result['count'] - 5} more")
                
                elif result.get('command') == '/download':
                    self.txt_command_output.append(f"Downloading {result['file']} from {result['domain']}")
                    self.txt_command_output.append(f"Download ID: {result['download_id']}")
                
                elif result.get('command') == '/p2p':
                    self.txt_command_output.append(f"Message sent to {result['domain']}")
                    self.txt_command_output.append(f"Message: {result['message']}")
                    self.txt_command_output.append(f"Timer: {result['timer']} seconds")
            else:
                self.txt_command_output.append(str(result))
        else:
            self.txt_command_output.append(f"Error: {result}")
        
        # Clear input
        self.txt_command_input.clear()
        
        # Scroll to bottom
        self.txt_command_output.moveCursor(QTextCursor.End)
    
    def p2p_message_dialog(self):
        """Open dialog for sending P2P messages."""
        sender = self.sender()
        domain_name = sender.property("domain")
        
        # Create dialog
        dialog = QDialog(self)
        dialog.setWindowTitle(f"P2P Message - {domain_name}")
        dialog.setMinimumSize(400, 300)
        
        layout = QVBoxLayout()
        
        # Message history
        history_label = QLabel(f"Message History with {domain_name}")
        message_history = self.network_client.get_message_history(domain_name)
        
        history_text = QTextEdit()
        history_text.setReadOnly(True)
        
        if message_history:
            for msg in message_history:
                sender_text = "You" if msg.get('sent', False) else domain_name
                timestamp = datetime.fromtimestamp(msg.get('timestamp', 0)).strftime('%H:%M:%S')
                history_text.append(f"[{timestamp}] {sender_text}: {msg.get('message', '')}")
        else:
            history_text.append("No message history")
        
        # Message input
        input_layout = QHBoxLayout()
        
        message_input = QTextEdit()
        message_input.setPlaceholderText("Type your message here...")
        message_input.setMaximumHeight(100)
        
        # Timer input
        timer_layout = QHBoxLayout()
        timer_label = QLabel("Timer (seconds):")
        timer_spin = QSpinBox()
        timer_spin.setMinimum(10)
        timer_spin.setMaximum(3600)
        timer_spin.setValue(300)
        
        timer_layout.addWidget(timer_label)
        timer_layout.addWidget(timer_spin)
        
        # Send button
        send_button = QPushButton("Send")
        
        def send_message():
            message = message_input.toPlainText()
            timer = timer_spin.value()
            
            if not message:
                return
            
            # Send message
            success, result = self.network_client.p2p_send_message(domain_name, message, timer)
            
            if success:
                # Add to history
                timestamp = datetime.now().strftime('%H:%M:%S')
                history_text.append(f"[{timestamp}] You: {message}")
                
                # Clear input
                message_input.clear()
                
                self.statusBar.showMessage(f"Message sent to {domain_name}", 3000)
            else:
                QMessageBox.warning(dialog, "Error", f"Failed to send message: {result}")
        
        send_button.clicked.connect(send_message)
        
        # Add widgets to layout
        layout.addWidget(history_label)
        layout.addWidget(history_text)
        layout.addWidget(QLabel("New Message:"))
        layout.addWidget(message_input)
        layout.addLayout(timer_layout)
        layout.addWidget(send_button)
        
        dialog.setLayout(layout)
        dialog.exec_()
    
    def create_domain_dialog(self):
        """Open dialog for creating a new domain."""
        dialog = QDialog(self)
        dialog.setWindowTitle("Create Domain")
        dialog.setMinimumSize(400, 300)
        
        layout = QVBoxLayout()
        
        # Domain name inputs
        name_layout = QFormLayout()
        
        prefix_input = QLineEdit()
        main_input = QLineEdit()
        suffix_input = QLineEdit()
        
        name_layout.addRow("Prefix:", prefix_input)
        name_layout.addRow("Main:", main_input)
        name_layout.addRow("Suffix:", suffix_input)
        
        # Domain title
        title_input = QLineEdit()
        name_layout.addRow("Title:", title_input)
        
        # Port
        port_spin = QSpinBox()
        port_spin.setMinimum(1024)
        port_spin.setMaximum(65535)
        port_spin.setValue(8844)
        name_layout.addRow("Port:", port_spin)
        
        # Create button
        create_button = QPushButton("Create Domain")
        
        def create_domain():
            prefix = prefix_input.text()
            main = main_input.text()
            suffix = suffix_input.text()
            title = title_input.text()
            port = port_spin.value()
            
            if not prefix or not main or not suffix:
                QMessageBox.warning(dialog, "Error", "All domain name parts are required")
                return
            
            # Create domain
            domain = Domain(
                prefix=prefix,
                main=main,
                suffix=suffix,
                ip=get_local_ip(),
                port=port
            )
            domain.title = title
            
            # Register domain
            success, message = self.network_client.register_domain(domain)
            
            if success:
                # Add to DDNS
                self.ddns.add_entry(domain.full_name, domain.ip, domain.port)
                
                # Store as my domain
                self.my_domain = domain
                
                # Start domain server if not running
                if not self.domain_server.is_running():
                    self.domain_server.domain = domain
                    self.domain_server.port = port
                    self.domain_server.start()
                    
                    # Update status
                    self.update_server_status()
                
                self.statusBar.showMessage(f"Domain {domain.full_name} created and registered", 3000)
                dialog.accept()
                
                # Refresh domains
                self.refresh_domains()
            else:
                QMessageBox.warning(dialog, "Error", f"Failed to register domain: {message}")
        
        create_button.clicked.connect(create_domain)
        
        # Add widgets to layout
        layout.addLayout(name_layout)
        layout.addWidget(create_button)
        
        dialog.setLayout(layout)
        dialog.exec_()
    
    def settings_dialog(self):
        """Open settings dialog."""
        dialog = QDialog(self)
        dialog.setWindowTitle("Settings")
        dialog.setMinimumSize(400, 300)
        
        layout = QVBoxLayout()
        
        # Server settings
        server_box = QGroupBox("Server Settings")
        server_layout = QFormLayout()
        
        port_spin = QSpinBox()
        port_spin.setMinimum(1024)
        port_spin.setMaximum(65535)
        port_spin.setValue(self.domain_server.port if self.domain_server else 8844)
        
        server_enabled = QCheckBox("Enable Server")
        server_enabled.setChecked(self.domain_server.is_running() if self.domain_server else False)
        
        server_layout.addRow("Port:", port_spin)
        server_layout.addRow(server_enabled)
        
        server_box.setLayout(server_layout)
        
        # Content settings
        content_box = QGroupBox("Content Settings")
        content_layout = QFormLayout()
        
        content_dir_input = QLineEdit(self.content_dir)
        content_dir_input.setReadOnly(True)
        
        browse_button = QPushButton("Browse")
        
        def browse_content_dir():
            dir_path = QFileDialog.getExistingDirectory(dialog, "Select Content Directory")
            if dir_path:
                content_dir_input.setText(dir_path)
        
        browse_button.clicked.connect(browse_content_dir)
        
        dir_layout = QHBoxLayout()
        dir_layout.addWidget(content_dir_input)
        dir_layout.addWidget(browse_button)
        
        content_layout.addRow("Content Directory:", dir_layout)
        
        content_box.setLayout(content_layout)
        
        # Save button
        save_button = QPushButton("Save Settings")
        
        def save_settings():
            # Update port
            new_port = port_spin.value()
            
            # Update server status
            if server_enabled.isChecked():
                if self.domain_server and not self.domain_server.is_running():
                    # Start server
                    self.domain_server.port = new_port
                    self.domain_server.start()
            else:
                if self.domain_server and self.domain_server.is_running():
                    # Stop server
                    self.domain_server.stop()
            
            # Update content directory
            new_content_dir = content_dir_input.text()
            if new_content_dir != self.content_dir:
                self.content_dir = new_content_dir
                if self.domain_server:
                    self.domain_server.content_dir = new_content_dir
            
            # Update status
            self.update_server_status()
            
            self.statusBar.showMessage("Settings saved", 3000)
            dialog.accept()
        
        save_button.clicked.connect(save_settings)
        
        # Add widgets to layout
        layout.addWidget(server_box)
        layout.addWidget(content_box)
        layout.addWidget(save_button)
        
        dialog.setLayout(layout)
        dialog.exec_()
    
    def update_server_status(self):
        """Update the server status indicator."""
        if self.domain_server and self.domain_server.is_running():
            self.lbl_server_status.setText(f"Server: Online (Port {self.domain_server.port})")
            if self.my_domain:
                self.lbl_server_status.setText(
                    f"Server: Online - {self.my_domain.full_name} (Port {self.domain_server.port})"
                )
        else:
            self.lbl_server_status.setText("Server: Offline")
    
    def cert_selected(self):
        """Handle certificate selection in the list."""
        selected_items = self.lst_certificates.selectedItems()
        if not selected_items:
            self.txt_cert_details.clear()
            return
        
        cert_name = selected_items[0].text()
        cert_path = os.path.join(self.security_manager.certs_dir, cert_name)
        
        try:
            # Get certificate details
            with open(cert_path, 'r') as f:
                cert_data = f.read()
            
            # Get fingerprint
            fingerprint = self.security_manager.get_certificate_fingerprint(cert_path)
            
            # Show details
            self.txt_cert_details.setText(f"Certificate: {cert_name}\n\nFingerprint: {fingerprint}\n\n{cert_data}")
            
        except Exception as e:
            self.txt_cert_details.setText(f"Error reading certificate: {str(e)}")
    
    def import_certificate(self):
        """Import a certificate."""
        file_path, _ = QFileDialog.getOpenFileName(self, "Import Certificate", "", "Certificates (*.cert *.pem)")
        if not file_path:
            return
        
        try:
            # Copy certificate to certs directory
            cert_name = os.path.basename(file_path)
            dest_path = os.path.join(self.security_manager.certs_dir, cert_name)
            
            with open(file_path, 'rb') as src_file:
                cert_data = src_file.read()
            
            with open(dest_path, 'wb') as dest_file:
                dest_file.write(cert_data)
            
            # Refresh certificate list
            self.refresh_certificates()
            
            self.statusBar.showMessage(f"Certificate {cert_name} imported", 3000)
            
        except Exception as e:
            QMessageBox.warning(self, "Error", f"Failed to import certificate: {str(e)}")
    
    def export_certificate(self):
        """Export a certificate."""
        selected_items = self.lst_certificates.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "Error", "No certificate selected")
            return
        
        cert_name = selected_items[0].text()
        cert_path = os.path.join(self.security_manager.certs_dir, cert_name)
        
        save_path, _ = QFileDialog.getSaveFileName(self, "Export Certificate", cert_name, "Certificates (*.cert *.pem)")
        if not save_path:
            return
        
        try:
            # Copy certificate to destination
            with open(cert_path, 'rb') as src_file:
                cert_data = src_file.read()
            
            with open(save_path, 'wb') as dest_file:
                dest_file.write(cert_data)
            
            self.statusBar.showMessage(f"Certificate exported to {save_path}", 3000)
            
        except Exception as e:
            QMessageBox.warning(self, "Error", f"Failed to export certificate: {str(e)}")
    
    def verify_certificate(self):
        """Verify a certificate."""
        selected_items = self.lst_certificates.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "Error", "No certificate selected")
            return
        
        cert_name = selected_items[0].text()
        cert_path = os.path.join(self.security_manager.certs_dir, cert_name)
        
        # Extract domain name from certificate name
        domain_name = cert_name.replace('.cert', '')
        
        try:
            # Verify certificate
            valid, message = self.security_manager.verify_certificate(cert_path, domain_name)
            
            if valid:
                QMessageBox.information(self, "Certificate Valid", f"Certificate for {domain_name} is valid.")
            else:
                QMessageBox.warning(self, "Certificate Invalid", f"Certificate validation failed: {message}")
            
        except Exception as e:
            QMessageBox.warning(self, "Error", f"Failed to verify certificate: {str(e)}")
    
    def delete_certificate(self):
        """Delete a certificate."""
        selected_items = self.lst_certificates.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "Error", "No certificate selected")
            return
        
        cert_name = selected_items[0].text()
        cert_path = os.path.join(self.security_manager.certs_dir, cert_name)
        
        # Confirm deletion
        confirm = QMessageBox.question(
            self, "Confirm Deletion",
            f"Are you sure you want to delete the certificate {cert_name}?",
            QMessageBox.Yes | QMessageBox.No
        )
        
        if confirm == QMessageBox.Yes:
            try:
                # Delete certificate
                os.remove(cert_path)
                
                # Refresh certificate list
                self.refresh_certificates()
                
                self.statusBar.showMessage(f"Certificate {cert_name} deleted", 3000)
                
            except Exception as e:
                QMessageBox.warning(self, "Error", f"Failed to delete certificate: {str(e)}")
    
    def refresh_certificates(self):
        """Refresh the certificate list."""
        self.lst_certificates.clear()
        
        try:
            # List certificates in directory
            cert_dir = self.security_manager.certs_dir
            cert_files = [f for f in os.listdir(cert_dir) if f.endswith('.cert')]
            
            for cert_file in cert_files:
                self.lst_certificates.addItem(cert_file)
            
        except Exception as e:
            self.statusBar.showMessage(f"Error refreshing certificates: {str(e)}", 5000)
    
    def closeEvent(self, event):
        """Handle window close event."""
        # Stop domain server if running
        if self.domain_server and self.domain_server.is_running():
            self.domain_server.stop()
        
        # Unregister domain if registered
        if self.my_domain:
            self.network_client.unregister_domain(self.my_domain.full_name)
        
        # Accept close event
        event.accept()

#####################################################################
# /decentrabrowser/client/app.py
#####################################################################

"""
Main entry point for the DecentraBrowser client application.
"""
import os
import sys
import argparse
from PyQt5.QtWidgets import QApplication

from ..shared.utils import setup_logging, ensure_directory_exists, get_data_dir
from ..shared.security import SecurityManager
from ..shared.ddns import DDNS
from ..shared.constants import DEFAULT_PORT, DEFAULT_CENTRAL_SERVER_PORT
from .network.http_server import DomainServer
from .network.client import NetworkClient
from .gui.main_window import MainWindow

# Setup logger
logger = setup_logging('client_app')

def main():
    """Entry point for the client application."""
    parser = argparse.ArgumentParser(description='DecentraBrowser Client')
    parser.add_argument('--port', type=int, default=DEFAULT_PORT,
                        help=f'Port to listen on (default: {DEFAULT_PORT})')
    parser.add_argument('--central-server', type=str, default='localhost',
                        help='Central server hostname (default: localhost)')
    parser.add_argument('--central-port', type=int, default=DEFAULT_CENTRAL_SERVER_PORT,
                        help=f'Central server port (default: {DEFAULT_CENTRAL_SERVER_PORT})')
    parser.add_argument('--data-dir', type=str, default=None,
                        help='Data directory (default: platform-specific user data dir)')
    
    args = parser.parse_args()
    
    # Setup data directory
    if args.data_dir:
        data_dir = args.data_dir
    else:
        data_dir = get_data_dir()
    
    ensure_directory_exists(data_dir)
    
    # Setup directories
    certs_dir = ensure_directory_exists(os.path.join(data_dir, 'certs'))
    content_dir = ensure_directory_exists(os.path.join(data_dir, 'content'))
    downloads_dir = ensure_directory_exists(os.path.join(data_dir, 'downloads'))
    
    # Setup DDNS
    ddns_file = os.path.join(data_dir, 'ddns.json')
    ddns = DDNS(ddns_file)
    
    # Setup security manager
    security_manager = SecurityManager(certs_dir)
    
    # Create RSA key pair if not exists
    if not os.path.exists(os.path.join(certs_dir, 'private_key.pem')):
        private_key, public_key = security_manager.generate_rsa_keypair()
        security_manager.save_private_key(private_key, 'private_key.pem')
        security_manager.save_public_key(public_key, 'public_key.pem')
        logger.info("Generated new RSA key pair")
    
    # Create domain server
    domain_server = DomainServer(None, security_manager, content_dir, port=args.port)
    
    # Create network client
    network_client = NetworkClient(security_manager, ddns, args.central_server, args.central_port)
    
    # Create and run application
    app = QApplication(sys.argv)
    main_window = MainWindow(network_client, domain_server, security_manager, ddns, content_dir)
    
    return app.exec_()

if __name__ == '__main__':
    sys.exit(main())

#####################################################################
# /decentrabrowser/tests/test_security.py
#####################################################################

"""
Tests for the security module.
"""
import os
import tempfile
import unittest
from cryptography.hazmat.primitives import serialization

from ..shared.security import SecurityManager

class TestSecurity(unittest.TestCase):
    """Test suite for the security module."""
    
    def setUp(self):
        """Set up test environment."""
        self.temp_dir = tempfile.TemporaryDirectory()
        self.certs_dir = self.temp_dir.name
        self.security_manager = SecurityManager(self.certs_dir)
    
    def tearDown(self):
        """Clean up test environment."""
        self.temp_dir.cleanup()
    
    def test_rsa_key_generation(self):
        """Test RSA key generation."""
        private_key, public_key = self.security_manager.generate_rsa_keypair(key_size=2048)
        
        # Check that keys are of the correct type
        self.assertEqual(private_key.key_size, 2048)
        self.assertEqual(public_key.key_size, 2048)
    
    def test_key_save_load(self):
        """Test saving and loading keys."""
        private_key, public_key = self.security_manager.generate_rsa_keypair(key_size=2048)
        
        # Save keys
        self.security_manager.save_private_key(private_key, 'test_private.pem')
        self.security_manager.save_public_key(public_key, 'test_public.pem')
        
        # Check that files exist
        self.assertTrue(os.path.exists(os.path.join(self.certs_dir, 'test_private.pem')))
        self.assertTrue(os.path.exists(os.path.join(self.certs_dir, 'test_public.pem')))
        
        # Load keys
        loaded_private = self.security_manager.load_private_key('test_private.pem')
        loaded_public = self.security_manager.load_public_key('test_public.pem')
        
        # Check that loaded keys match original
        private_bytes = private_key.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.PKCS8,
            encryption_algorithm=serialization.NoEncryption()
        )
        loaded_private_bytes = loaded_private.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.PKCS8,
            encryption_algorithm=serialization.NoEncryption()
        )
        
        public_bytes = public_key.public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo
        )
        loaded_public_bytes = loaded_public.public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo
        )
        
        self.assertEqual(private_bytes, loaded_private_bytes)
        self.assertEqual(public_bytes, loaded_public_bytes)
    
    def test_rsa_encryption(self):
        """Test RSA encryption and decryption."""
        private_key, public_key = self.security_manager.generate_rsa_keypair(key_size=2048)
        
        # Test data
        test_data = b"This is a test message"
        
        # Encrypt
        encrypted = self.security_manager.encrypt_with_rsa(public_key, test_data)
        
        # Decrypt
        decrypted = self.security_manager.decrypt_with_rsa(private_key, encrypted)
        
        # Check that decrypted data matches original
        self.assertEqual(test_data, decrypted)
    
    def test_aes_encryption(self):
        """Test AES encryption and decryption."""
        # Generate key
        key = self.security_manager.generate_aes_key(key_size=256)
        
        # Test data
        test_data = b"This is a test message for AES encryption"
        
        # Encrypt
        encrypted = self.security_manager.encrypt_with_aes(key, test_data)
        
        # Decrypt
        decrypted = self.security_manager.decrypt_with_aes(key, encrypted)
        
        # Check that decrypted data matches original
        self.assertEqual(test_data, decrypted)
    
    def test_certificate_generation(self):
        """Test certificate generation and verification."""
        private_key, _ = self.security_manager.generate_rsa_keypair(key_size=2048)
        
        # Generate certificate
        domain_name = "test.example.domain"
        cert_path = self.security_manager.generate_certificate(private_key, domain_name)
        
        # Check that certificate file exists
        self.assertTrue(os.path.exists(cert_path))
        
        # Verify certificate
        valid, _ = self.security_manager.verify_certificate(cert_path, domain_name)
        self.assertTrue(valid)
        
        # Verify with wrong domain
        valid, _ = self.security_manager.verify_certificate(cert_path, "wrong.domain")
        self.assertFalse(valid)
    
    def test_pfs_session(self):
        """Test Perfect Forward Secrecy session."""
        # Initialize PFS session for client
        client_session = self.security_manager.pfs_init()
        
        # Initialize PFS session for server
        server_session = self.security_manager.pfs_init()
        
        # Get public keys
        client_public_key = client_session['public_key'].public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo
        )
        
        server_public_key = server_session['public_key'].public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo
        )
        
        # Exchange keys
        encrypted_secret = self.security_manager.pfs_exchange(client_session, server_public_key)
        
        # Receive on server side
        self.security_manager.pfs_receive(server_session, encrypted_secret)
        
        # Test message encryption/decryption
        test_message = b"This is a secure PFS message"
        
        # Client encrypts
        encrypted_message = self.security_manager.pfs_encrypt(client_session, test_message)
        
        # Server decrypts
        decrypted_message = self.security_manager.pfs_decrypt(server_session, encrypted_message)
        
        # Check that decrypted message matches original
        self.assertEqual(test_message, decrypted_message)

#####################################################################
# /decentrabrowser/tests/test_domain.py
#####################################################################

"""
Tests for the domain module.
"""
import os
import tempfile
import unittest
import json
from datetime import datetime

from ..shared.domain import Domain, DomainRegistry

class TestDomain(unittest.TestCase):
    """Test suite for the domain module."""
    
    def test_domain_creation(self):
        """Test domain creation."""
        domain = Domain('test', 'example', 'com', '192.168.1.1', 8080)
        
        self.assertEqual(domain.prefix, 'test')
        self.assertEqual(domain.main, 'example')
        self.assertEqual(domain.suffix, 'com')
        self.assertEqual(domain.ip, '192.168.1.1')
        self.assertEqual(domain.port, 8080)
        self.assertEqual(domain.full_name, 'test.example.com.domain')
        self.assertEqual(domain.address, '192.168.1.1:8080')
    
    def test_domain_from_full_name(self):
        """Test creating a domain from a full name."""
        domain = Domain.from_full_name('test.example.com.domain', '192.168.1.1', 8080)
        
        self.assertEqual(domain.prefix, 'test')
        self.assertEqual(domain.main, 'example')
        self.assertEqual(domain.suffix, 'com')
        self.assertEqual(domain.ip, '192.168.1.1')
        self.assertEqual(domain.port, 8080)
        self.assertEqual(domain.full_name, 'test.example.com.domain')
    
    def test_domain_to_dict(self):
        """Test converting a domain to a dictionary."""
        domain = Domain('test', 'example', 'com', '192.168.1.1', 8080)
        domain.title = "Test Domain"
        domain.favicon = "test_favicon"
        domain.banner = "test_banner"
        domain.file_count = 10
        
        domain_dict = domain.to_dict()
        
        self.assertEqual(domain_dict['prefix'], 'test')
        self.assertEqual(domain_dict['main'], 'example')
        self.assertEqual(domain_dict['suffix'], 'com')
        self.assertEqual(domain_dict['full_name'], 'test.example.com.domain')
        self.assertEqual(domain_dict['ip'], '192.168.1.1')
        self.assertEqual(domain_dict['port'], 8080)
        self.assertEqual(domain_dict['address'], '192.168.1.1:8080')
        self.assertEqual(domain_dict['title'], 'Test Domain')
        self.assertEqual(domain_dict['favicon'], 'test_favicon')
        self.assertEqual(domain_dict['banner'], 'test_banner')
        self.assertEqual(domain_dict['file_count'], 10)
    
    def test_domain_from_dict(self):
        """Test creating a domain from a dictionary."""
        domain_dict = {
            'prefix': 'test',
            'main': 'example',
            'suffix': 'com',
            'ip': '192.168.1.1',
            'port': 8080,
            'title': 'Test Domain',
            'favicon': 'test_favicon',
            'banner': 'test_banner',
            'file_count': 10
        }
        
        domain = Domain.from_dict(domain_dict)
        
        self.assertEqual(domain.prefix, 'test')
        self.assertEqual(domain.main, 'example')
        self.assertEqual(domain.suffix, 'com')
        self.assertEqual(domain.full_name, 'test.example.com.domain')
        self.assertEqual(domain.ip, '192.168.1.1')
        self.assertEqual(domain.port, 8080)
        self.assertEqual(domain.title, 'Test Domain')
        self.assertEqual(domain.favicon, 'test_favicon')
        self.assertEqual(domain.banner, 'test_banner')
        self.assertEqual(domain.file_count, 10)


class TestDomainRegistry(unittest.TestCase):
    """Test suite for the domain registry."""
    
    def setUp(self):
        """Set up test environment."""
        self.temp_dir = tempfile.TemporaryDirectory()
        self.registry_file = os.path.join(self.temp_dir.name, 'registry.json')
        self.registry = DomainRegistry(self.registry_file)
    
    def tearDown(self):
        """Clean up test environment."""
        self.temp_dir.cleanup()
    
    def test_registry_add_remove(self):
        """Test adding and removing domains from the registry."""
        # Create domain
        domain = Domain('test', 'example', 'com', '192.168.1.1', 8080)
        
        # Add to registry
        self.registry.register(domain)
        
        # Check that domain was added
        self.assertIn(domain.full_name, self.registry.domains)
        
        # Check that file was created
        self.assertTrue(os.path.exists(self.registry_file))
        
        # Remove from registry
        self.registry.unregister(domain.full_name)
        
        # Check that domain was removed
        self.assertNotIn(domain.full_name, self.registry.domains)
    
    def test_registry_persistence(self):
        """Test registry persistence."""
        # Create domain
        domain = Domain('test', 'example', 'com', '192.168.1.1', 8080)
        
        # Add to registry
        self.registry.register(domain)
        
        # Create new registry instance with same file
        new_registry = DomainRegistry(self.registry_file)
        
        # Check that domain was loaded
        self.assertIn(domain.full_name, new_registry.domains)
        
        # Check that domain properties match
        loaded_domain = new_registry.domains[domain.full_name]
        self.assertEqual(loaded_domain.prefix, domain.prefix)
        self.assertEqual(loaded_domain.main, domain.main)
        self.assertEqual(loaded_domain.suffix, domain.suffix)
        self.assertEqual(loaded_domain.ip, domain.ip)
        self.assertEqual(loaded_domain.port, domain.port)
    
    def test_registry_update(self):
        """Test updating domain in registry."""
        # Create domain
        domain = Domain('test', 'example', 'com', '192.168.1.1', 8080)
        
        # Add to registry
        self.registry.register(domain)
        
        # Update domain
        self.registry.update_domain(domain.full_name, ip='192.168.1.2', port=8081, title='Updated Domain')
        
        # Check that domain was updated
        updated_domain = self.registry.domains[domain.full_name]
        self.assertEqual(updated_domain.ip, '192.168.1.2')
        self.assertEqual(updated_domain.port, 8081)
        self.assertEqual(updated_domain.title, 'Updated Domain')

#####################################################################
# /decentrabrowser/scripts/install.py
#####################################################################

"""
Installation script for the DecentraBrowser system.
"""
import os
import sys
import argparse
import subprocess
import platform
import shutil
from pathlib import Path

def check_python_version():
    """Check that Python version is at least 3.8."""
    if sys.version_info < (3, 8):
        print("Error: Python 3.8 or higher is required")
        sys.exit(1)

def install_dependencies():
    """Install Python dependencies."""
    print("Installing dependencies...")
    
    # Install requirements
    subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"])
    
    print("Dependencies installed successfully")

def create_data_directory():
    """Create data directory."""
    home = Path.home()
    
    if platform.system() == "Windows":
        data_dir = home / "AppData" / "Local" / "DecentraBrowser"
    elif platform.system() == "Darwin":
        data_dir = home / "Library" / "Application Support" / "DecentraBrowser"
    else:
        data_dir = home / ".decentrabrowser"
    
    # Create directories
    os.makedirs(data_dir, exist_ok=True)
    os.makedirs(data_dir / "certs", exist_ok=True)
    os.makedirs(data_dir / "content", exist_ok=True)
    os.makedirs(data_dir / "downloads", exist_ok=True)
    
    print(f"Data directory created at {data_dir}")
    
    return data_dir

def create_desktop_shortcut(install_dir):
    """Create desktop shortcut."""
    home = Path.home()
    desktop = home / "Desktop"
    
    if platform.system() == "Windows":
        # Create Windows shortcut
        shortcut_path = desktop / "DecentraBrowser.lnk"
        
        # Use Windows script to create shortcut
        vbs_script = """
        Set oWS = WScript.CreateObject("WScript.Shell")
        sLinkFile = "{0}"
        Set oLink = oWS.CreateShortcut(sLinkFile)
        oLink.TargetPath = "{1}"
        oLink.Save
        """.format(str(shortcut_path), str(install_dir / "run.bat"))
        
        with open("create_shortcut.vbs", "w") as f:
            f.write(vbs_script)
        
        subprocess.call(["cscript", "create_shortcut.vbs"])
        os.remove("create_shortcut.vbs")
        
    elif platform.system() == "Darwin":
        # Create macOS .command file
        command_path = desktop / "DecentraBrowser.command"
        
        with open(command_path, "w") as f:
            f.write(f"#!/bin/bash\ncd {install_dir} && python -m decentrabrowser.client.app")
        
        # Make executable
        os.chmod(command_path, 0o755)
        
    else:
        # Create Linux .desktop file
        desktop_file = """
        [Desktop Entry]
        Version=1.0
        Type=Application
        Name=DecentraBrowser
        Comment=Decentralized Browser
        Exec=python -m decentrabrowser.client.app
        Path={0}
        Terminal=false
        Categories=Network;
        """.format(str(install_dir))
        
        desktop_path = desktop / "DecentraBrowser.desktop"
        
        with open(desktop_path, "w") as f:
            f.write(desktop_file)
        
        # Make executable
        os.chmod(desktop_path, 0o755)
    
    print(f"Desktop shortcut created at {desktop}")

def create_startup_scripts(install_dir):
    """Create startup scripts."""
    if platform.system() == "Windows":
        # Create batch file
        bat_path = install_dir / "run.bat"
        
        with open(bat_path, "w") as f:
            f.write(f"@echo off\ncd {install_dir}\npython -m decentrabrowser.client.app")
        
    elif platform.system() == "Darwin" or platform.system() == "Linux":
        # Create shell script
        sh_path = install_dir / "run.sh"
        
        with open(sh_path, "w") as f:
            f.write(f"#!/bin/bash\ncd {install_dir} && python -m decentrabrowser.client.app")
        
        # Make executable
        os.chmod(sh_path, 0o755)
    
    print("Startup scripts created")

def main():
    """Main installation function."""
    parser = argparse.ArgumentParser(description="DecentraBrowser Installation Script")
    parser.add_argument("--install-dir", type=str, default=None,
                        help="Installation directory (default: current directory)")
    parser.add_argument("--no-shortcut", action="store_true",
                        help="Don't create desktop shortcut")
    
    args = parser.parse_args()
    
    print("DecentraBrowser Installation")
    print("===========================")
    
    # Check Python version
    check_python_version()
    
    # Determine installation directory
    if args.install_dir:
        install_dir = Path(args.install_dir)
    else:
        install_dir = Path.cwd()
    
    print(f"Installing to: {install_dir}")
    
    # Install dependencies
    install_dependencies()
    
    # Create data directory
    data_dir = create_data_directory()
    
    # Create startup scripts
    create_startup_scripts(install_dir)
    
    # Create desktop shortcut
    if not args.no_shortcut:
        create_desktop_shortcut(install_dir)
    
    print("\nInstallation Complete!")
    print(f"To start DecentraBrowser, run: python -m decentrabrowser.client.app")

if __name__ == "__main__":
    main()

#####################################################################
# /decentrabrowser/scripts/deploy.py
#####################################################################

"""
Deployment script for the DecentraBrowser system.
"""
import os
import sys
import argparse
import subprocess
import platform
import shutil
import zipfile
from pathlib import Path

def build_package():
    """Build package for deployment."""
    print("Building package...")
    
    # Clean build directory
    if os.path.exists("build"):
        shutil.rmtree("build")
    
    if os.path.exists("dist"):
        shutil.rmtree("dist")
    
    # Create setup.py for building
    setup_py = """
from setuptools import setup, find_packages

setup(
    name="decentrabrowser",
    version="0.1.0",
    packages=find_packages(),
    install_requires=[
        "cryptography>=3.4.0",
        "PyQt5>=5.15.4",
        "flask>=2.0.1",
        "requests>=2.25.1",
        "pyOpenSSL>=20.0.1",
        "pycryptodome>=3.10.1",
        "click>=8.0.0",
        "python-dotenv>=0.19.0",
        "tinydb>=4.5.0",
        "marshmallow>=3.13.0",
        "aiohttp>=3.7.4",
        "sqlalchemy>=1.4.23",
        "pynacl>=1.4.0",
        "qrcode>=7.3.1",
        "pillow>=8.3.1",
        "PyQtWebEngine>=5.15.4",
        "colorama>=0.4.4",
    ],
    entry_points={
        "console_scripts": [
            "decentrabrowser-client=decentrabrowser.client.app:main",
            "decentrabrowser-server=decentrabrowser.central_server.app:main",
        ],
    },
)
"""
    
    with open("setup.py", "w") as f:
        f.write(setup_py)
    
    # Build package
    subprocess.check_call([sys.executable, "setup.py", "sdist", "bdist_wheel"])
    
    print("Package built successfully")

def build_executable():
    """Build executable for deployment."""
    print("Building executable...")
    
    # Check PyInstaller installation
    try:
        import PyInstaller
    except ImportError:
        print("Installing PyInstaller...")
        subprocess.check_call([sys.executable, "-m", "pip", "install", "pyinstaller"])
    
    # Determine spec file
    if platform.system() == "Windows":
        icon_option = "--icon=resources/icon.ico"
    elif platform.system() == "Darwin":
        icon_option = "--icon=resources/icon.icns"
    else:
        icon_option = "--icon=resources/icon.png"
    
    # Build client executable
    print("Building client executable...")
    subprocess.check_call([
        "pyinstaller",
        "--name=DecentraBrowser",
        "--onefile",
        icon_option,
        "--windowed",
        "--add-data=resources;resources",
        "decentrabrowser/client/app.py"
    ])
    
    # Build server executable
    print("Building server executable...")
    subprocess.check_call([
        "pyinstaller",
        "--name=DecentraBrowserServer",
        "--onefile",
        icon_option,
        "decentrabrowser/central_server/app.py"
    ])
    
    print("Executables built successfully")

def create_installer():
    """Create installer for deployment."""
    print("Creating installer...")
    
    if platform.system() == "Windows":
        # Create Inno Setup script
        iss_script = """
        [Setup]
        AppName=DecentraBrowser
        AppVersion=0.1.0
        DefaultDirName={autopf}\\DecentraBrowser
        DefaultGroupName=DecentraBrowser
        UninstallDisplayIcon={app}\\DecentraBrowser.exe
        Compression=lzma2
        SolidCompression=yes
        OutputDir=installer
        OutputBaseFilename=DecentraBrowser_Setup
        
        [Files]
        Source: "dist\\DecentraBrowser.exe"; DestDir: "{app}"; Flags: ignoreversion
        Source: "dist\\DecentraBrowserServer.exe"; DestDir: "{app}"; Flags: ignoreversion
        Source: "resources\\*"; DestDir: "{app}\\resources"; Flags: ignoreversion recursesubdirs
        
        [Icons]
        Name: "{group}\\DecentraBrowser"; Filename: "{app}\\DecentraBrowser.exe"
        Name: "{group}\\DecentraBrowser Server"; Filename: "{app}\\DecentraBrowserServer.exe"
        Name: "{commondesktop}\\DecentraBrowser"; Filename: "{app}\\DecentraBrowser.exe"
        """
        
        os.makedirs("installer", exist_ok=True)
        
        with open("installer.iss", "w") as f:
            f.write(iss_script)
        
        # Run Inno Setup Compiler
        subprocess.check_call(["iscc", "installer.iss"])
        
    elif platform.system() == "Darwin":
        # Create macOS DMG
        subprocess.check_call([
            "hdiutil",
            "create",
            "-volname",
            "DecentraBrowser",
            "-srcfolder",
            "dist/DecentraBrowser.app",
            "-ov",
            "-format",
            "UDZO",
            "installer/DecentraBrowser.dmg"
        ])
        
    else:
        # Create Linux installer using fpm
        try:
            subprocess.check_call(["fpm", "--version"])
        except (subprocess.CalledProcessError, FileNotFoundError):
            print("fpm not found. Please install fpm to create Linux packages.")
            print("See: https://fpm.readthedocs.io/en/latest/installation.html")
            return
        
        # Create deb package
        subprocess.check_call([
            "fpm",
            "-s", "dir",
            "-t", "deb",
            "-n", "decentrabrowser",
            "-v", "0.1.0",
            "--prefix=/usr/local",
            "dist/DecentraBrowser=/usr/local/bin/",
            "dist/DecentraBrowserServer=/usr/local/bin/",
            "resources/=/usr/local/share/decentrabrowser/resources/"
        ])
        
        # Create rpm package
        subprocess.check_call([
            "fpm",
            "-s", "dir",
            "-t", "rpm",
            "-n", "decentrabrowser",
            "-v", "0.1.0",
            "--prefix=/usr/local",
            "dist/DecentraBrowser=/usr/local/bin/",
            "dist/DecentraBrowserServer=/usr/local/bin/",
            "resources/=/usr/local/share/decentrabrowser/resources/"
        ])
    
    print("Installer created successfully")

def create_zip_package():
    """Create ZIP package for deployment."""
    print("Creating ZIP package...")
    
    # Create dist directory if it doesn't exist
    os.makedirs("dist", exist_ok=True)
    
    # Determine ZIP filename
    if platform.system() == "Windows":
        zip_filename = "dist/DecentraBrowser_Windows.zip"
    elif platform.system() == "Darwin":
        zip_filename = "dist/DecentraBrowser_macOS.zip"
    else:
        zip_filename = "dist/DecentraBrowser_Linux.zip"
    
    # Create ZIP file
    with zipfile.ZipFile(zip_filename, "w", zipfile.ZIP_DEFLATED) as zipf:
        # Add executables
        if platform.system() == "Windows":
            zipf.write("dist/DecentraBrowser.exe", "DecentraBrowser.exe")
            zipf.write("dist/DecentraBrowserServer.exe", "DecentraBrowserServer.exe")
        else:
            zipf.write("dist/DecentraBrowser", "DecentraBrowser")
            zipf.write("dist/DecentraBrowserServer", "DecentraBrowserServer")
        
        # Add resources
        for root, dirs, files in os.walk("resources"):
            for file in files:
                file_path = os.path.join(root, file)
                zipf.write(file_path, os.path.relpath(file_path, "resources"))
        
        # Add README and LICENSE
        if os.path.exists("README.md"):
            zipf.write("README.md")
        if os.path.exists("LICENSE"):
            zipf.write("LICENSE")
    
    print(f"ZIP package created at {zip_filename}")

def main():
    """Main deployment function."""
    parser = argparse.ArgumentParser(description="DecentraBrowser Deployment Script")
    parser.add_argument("--package", action="store_true",
                        help="Build Python package")
    parser.add_argument("--exe", action="store_true",
                        help="Build executable")
    parser.add_argument("--installer", action="store_true",
                        help="Create installer")
    parser.add_argument("--zip", action="store_true",
                        help="Create ZIP package")
    parser.add_argument("--all", action="store_true",
                        help="Build all deployment artifacts")
    
    args = parser.parse_args()
    
    print("DecentraBrowser Deployment")
    print("=========================")
    
    # Default to building package if no options specified
    if not (args.package or args.exe or args.installer or args.zip or args.all):
        args.package = True
    
    # Build all if --all specified
    if args.all:
        args.package = True
        args.exe = True
        args.installer = True
        args.zip = True
    
    # Build package
    if args.package:
        build_package()
    
    # Build executable
    if args.exe:
        build_executable()
    
    # Create installer
    if args.installer:
        create_installer()
    
    # Create ZIP package
    if args.zip:
        create_zip_package()
    
    print("\nDeployment Complete!")

if __name__ == "__main__":
    main()

#####################################################################
# README.md
#####################################################################

"""
# DecentraBrowser

A decentralized browser system with custom DDNS, encryption, and P2P communication features.

## Overview

DecentraBrowser is a Python-based implementation of a decentralized browsing system that enables:

- Custom domain naming with format `prefix.main.suffix.domain`
- Perfect Forward Secrecy (PFS) for secure communications
- RSA and AES encryption for secure data transfer
- P2P messaging between domain owners and users
- Ephemeral domains that exist only while servers are active

## Installation

### Prerequisites

- Python 3.8 or higher
- PyQt5 for the GUI
- Flask for the central server
- Cryptography libraries for security features

### Quick Installation

```bash
# Clone the repository
git clone https://github.com/yourusername/decentrabrowser.git
cd decentrabrowser

# Run the installation script
python decentrabrowser/scripts/install.py
```

### Manual Installation

```bash
# Install dependencies
pip install -r requirements.txt

# Run the client
python -m decentrabrowser.client.app

# Run the central server (separate terminal)
python -m decentrabrowser.central_server.app
```

## Usage

### Client

The client application provides a GUI for browsing and connecting to domains, downloading files, and sending P2P messages.

- **Domains Tab**: Browse available domains and connect to them
- **Files Tab**: View and download files from connected domains
- **Command Tab**: Interact with the system using command-line interface
- **Certificates Tab**: Manage encryption certificates

### Command Line Interface

The client supports the following commands:

- `/sources`: List all available domains
- `/download filename domainname [save_dir]`: Download a file from a domain
- `/p2p domainname timer message`: Send a P2P message to a domain

### Creating a Domain

1. Click "Create Domain" in the toolbar
2. Enter domain name parts (prefix, main, suffix)
3. Set a title and port
4. Click "Create Domain" to register with the central server

## Architecture

DecentraBrowser consists of three main components:

1. **Central Server**: Manages domain registration and discovery
2. **Client Browser**: Connects to domains and displays content
3. **Domain Server**: Serves content from a local directory

### Security Features

- **RSA Key Pairs**: Used for authentication and key exchange
- **AES Encryption**: Used for efficient encryption of data
- **PFS Sessions**: Ensure future key compromise doesn't affect current communications
- **Certificate Management**: Verifies domain ownership and authenticity

## Development

### Project Structure

```
/decentrabrowser/
  ├── central_server/      # Central registration server
  ├── client/              # Client browser application
  │   ├── gui/             # GUI components
  │   ├── network/         # Network utilities
  │   └── security/        # Security and encryption
  ├── shared/              # Shared utilities and protocols
  ├── docs/                # Documentation
  └── tests/               # Test suite
```

### Running Tests

```bash
# Run all tests
python -m unittest discover

# Run specific test module
python -m unittest decentrabrowser.tests.test_security
```

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## License

This project is licensed under the MIT License - see the LICENSE file for details.
"""

#####################################################################
# Example Domain Setup
#####################################################################

"""
# Example of registering and setting up a domain

from decentrabrowser.shared.utils import get_local_ip, setup_logging, ensure_directory_exists
from decentrabrowser.shared.security import SecurityManager
from decentrabrowser.shared.domain import Domain
from decentrabrowser.client.network.http_server import DomainServer
from decentrabrowser.client.network.client import NetworkClient
from decentrabrowser.shared.ddns import DDNS
import os

# Setup logging
logger = setup_logging('example_setup')

# Setup directories
data_dir = "decentrabrowser_data"
ensure_directory_exists(data_dir)
certs_dir = ensure_directory_exists(os.path.join(data_dir, 'certs'))
content_dir = ensure_directory_exists(os.path.join(data_dir, 'content'))

# Setup security manager
security_manager = SecurityManager(certs_dir)

# Create RSA key pair if not exists
if not os.path.exists(os.path.join(certs_dir, 'private_key.pem')):
    private_key, public_key = security_manager.generate_rsa_keypair()
    security_manager.save_private_key(private_key, 'private_key.pem')
    security_manager.save_public_key(public_key, 'public_key.pem')
    logger.info("Generated new RSA key pair")

# Setup DDNS
ddns_file = os.path.join(data_dir, 'ddns.json')
ddns = DDNS(ddns_file)

# Create network client
network_client = NetworkClient(security_manager, ddns, 'localhost', 8800)

# Create domain
domain = Domain(
    prefix='example',
    main='test',
    suffix='net',
    ip=get_local_ip(),
    port=8844
)
domain.title = "Example Test Domain"

# Register domain
success, message = network_client.register_domain(domain)
if success:
    logger.info(f"Domain registered: {domain.full_name}")
    
    # Add to DDNS
    ddns.add_entry(domain.full_name, domain.ip, domain.port)
    
    # Create domain server
    domain_server = DomainServer(domain, security_manager, content_dir, port=domain.port)
    
    # Start domain server
    if domain_server.start():
        logger.info(f"Domain server started on port {domain.port}")
        
        # Keep server running
        try:
            input("Press Enter to stop the server...")
        except KeyboardInterrupt:
            pass
        finally:
            # Stop server and unregister domain
            domain_server.stop()
            network_client.unregister_domain(domain.full_name)
            logger.info("Domain server stopped and domain unregistered")
    else:
        logger.error("Failed to start domain server")
else:
    logger.error(f"Failed to register domain: {message}")
"""
    