This commit is contained in:
parent
0b52d602ef
commit
71b9cc787b
430
protocol_prototype/auto_mode.py
Normal file
430
protocol_prototype/auto_mode.py
Normal file
@ -0,0 +1,430 @@
|
||||
import time
|
||||
import threading
|
||||
import queue
|
||||
from typing import Optional, Dict, Any, List, Callable, Tuple
|
||||
|
||||
# ANSI colors for logging
|
||||
RED = "\033[91m"
|
||||
GREEN = "\033[92m"
|
||||
YELLOW = "\033[93m"
|
||||
BLUE = "\033[94m"
|
||||
RESET = "\033[0m"
|
||||
|
||||
class AutoModeConfig:
|
||||
"""Configuration parameters for the automatic mode behavior."""
|
||||
def __init__(self):
|
||||
# Ping behavior
|
||||
self.ping_response_accept = True # Whether to accept incoming pings
|
||||
self.ping_auto_initiate = False # Whether to initiate pings when connected
|
||||
self.ping_retry_count = 3 # Number of ping retries
|
||||
self.ping_retry_delay = 5.0 # Seconds between ping retries
|
||||
self.ping_timeout = 10.0 # Seconds to wait for ping response
|
||||
self.preferred_cipher = 0 # 0=AES-GCM, 1=ChaCha20-Poly1305
|
||||
|
||||
# Handshake behavior
|
||||
self.handshake_retry_count = 3 # Number of handshake retries
|
||||
self.handshake_retry_delay = 5.0 # Seconds between handshake retries
|
||||
self.handshake_timeout = 10.0 # Seconds to wait for handshake
|
||||
|
||||
# Messaging behavior
|
||||
self.auto_message_enabled = False # Whether to auto-send messages
|
||||
self.message_interval = 10.0 # Seconds between auto messages
|
||||
self.message_content = "Hello, secure world!" # Default message
|
||||
|
||||
# General behavior
|
||||
self.active_mode = False # If true, initiates protocol instead of waiting
|
||||
|
||||
|
||||
class AutoMode:
|
||||
"""
|
||||
Manages automated behavior for the Icing protocol.
|
||||
Handles automatic progression through the protocol stages:
|
||||
1. Connection setup
|
||||
2. Ping/discovery
|
||||
3. Key exchange
|
||||
4. Encrypted communication
|
||||
"""
|
||||
|
||||
def __init__(self, protocol_interface):
|
||||
"""
|
||||
Initialize the AutoMode manager.
|
||||
|
||||
Args:
|
||||
protocol_interface: An object implementing the required protocol methods
|
||||
"""
|
||||
self.protocol = protocol_interface
|
||||
self.config = AutoModeConfig()
|
||||
self.active = False
|
||||
self.state = "idle"
|
||||
|
||||
# Message queue for automated sending
|
||||
self.message_queue = queue.Queue()
|
||||
|
||||
# Tracking variables
|
||||
self.ping_attempts = 0
|
||||
self.handshake_attempts = 0
|
||||
self.last_action_time = 0
|
||||
self.timer_tasks = [] # List of active timer tasks (for cleanup)
|
||||
|
||||
def start(self):
|
||||
"""Start the automatic mode."""
|
||||
if self.active:
|
||||
return
|
||||
|
||||
self.active = True
|
||||
self.state = "idle"
|
||||
self.ping_attempts = 0
|
||||
self.handshake_attempts = 0
|
||||
self.last_action_time = time.time()
|
||||
|
||||
self._log_info("Automatic mode started")
|
||||
|
||||
# Start in active mode if configured
|
||||
if self.config.active_mode and self.protocol.connections:
|
||||
self._start_ping_sequence()
|
||||
|
||||
def stop(self):
|
||||
"""Stop the automatic mode and clean up any pending tasks."""
|
||||
if not self.active:
|
||||
return
|
||||
|
||||
# Cancel any pending timers
|
||||
for timer in self.timer_tasks:
|
||||
if timer.is_alive():
|
||||
timer.cancel()
|
||||
self.timer_tasks = []
|
||||
|
||||
self.active = False
|
||||
self.state = "idle"
|
||||
self._log_info("Automatic mode stopped")
|
||||
|
||||
def handle_connection_established(self):
|
||||
"""Called when a new connection is established."""
|
||||
if not self.active:
|
||||
return
|
||||
|
||||
self._log_info("Connection established")
|
||||
|
||||
# If in active mode, start pinging
|
||||
if self.config.active_mode:
|
||||
self._start_ping_sequence()
|
||||
|
||||
def handle_ping_received(self, index: int):
|
||||
"""
|
||||
Handle a received ping request.
|
||||
|
||||
Args:
|
||||
index: Index of the ping request in the protocol's inbound message queue
|
||||
"""
|
||||
if not self.active or not self._is_valid_message_index(index):
|
||||
return
|
||||
|
||||
self._log_info(f"Ping request received (index={index})")
|
||||
|
||||
# Automatically respond to ping if configured to accept
|
||||
if self.config.ping_response_accept:
|
||||
self._log_info(f"Auto-responding to ping with accept={self.config.ping_response_accept}")
|
||||
try:
|
||||
# Schedule the response with a small delay to simulate real behavior
|
||||
timer = threading.Timer(0.5, self._respond_to_ping, args=[index])
|
||||
timer.daemon = True
|
||||
timer.start()
|
||||
self.timer_tasks.append(timer)
|
||||
except Exception as e:
|
||||
self._log_error(f"Failed to auto-respond to ping: {e}")
|
||||
|
||||
def handle_ping_response_received(self, accepted: bool):
|
||||
"""
|
||||
Handle a received ping response.
|
||||
|
||||
Args:
|
||||
accepted: Whether the ping was accepted
|
||||
"""
|
||||
if not self.active:
|
||||
return
|
||||
|
||||
self.ping_attempts = 0 # Reset ping attempts counter
|
||||
|
||||
if accepted:
|
||||
self._log_info("Ping accepted! Proceeding with handshake")
|
||||
# Send handshake if not already done
|
||||
if self.state != "handshake_sent":
|
||||
self._ensure_ephemeral_keys()
|
||||
self._start_handshake_sequence()
|
||||
else:
|
||||
self._log_info("Ping rejected by peer. Stopping auto-protocol sequence.")
|
||||
self.state = "idle"
|
||||
|
||||
def handle_handshake_received(self, index: int):
|
||||
"""
|
||||
Handle a received handshake.
|
||||
|
||||
Args:
|
||||
index: Index of the handshake in the protocol's inbound message queue
|
||||
"""
|
||||
if not self.active or not self._is_valid_message_index(index):
|
||||
return
|
||||
|
||||
self._log_info(f"Handshake received (index={index})")
|
||||
|
||||
try:
|
||||
# Ensure we have ephemeral keys
|
||||
self._ensure_ephemeral_keys()
|
||||
|
||||
# Process the handshake (compute ECDH)
|
||||
self.protocol.generate_ecdhe(index)
|
||||
|
||||
# Derive HKDF key
|
||||
self.protocol.derive_hkdf()
|
||||
|
||||
# If we haven't sent our handshake yet, send it
|
||||
if self.state != "handshake_sent":
|
||||
timer = threading.Timer(0.5, self.protocol.send_handshake)
|
||||
timer.daemon = True
|
||||
timer.start()
|
||||
self.timer_tasks.append(timer)
|
||||
self.state = "handshake_sent"
|
||||
else:
|
||||
self.state = "key_exchange_complete"
|
||||
|
||||
# Start sending queued messages if auto messaging is enabled
|
||||
if self.config.auto_message_enabled:
|
||||
self._start_message_sequence()
|
||||
|
||||
except Exception as e:
|
||||
self._log_error(f"Failed to process handshake: {e}")
|
||||
|
||||
def handle_encrypted_received(self, index: int):
|
||||
"""
|
||||
Handle a received encrypted message.
|
||||
|
||||
Args:
|
||||
index: Index of the encrypted message in the protocol's inbound message queue
|
||||
"""
|
||||
if not self.active or not self._is_valid_message_index(index):
|
||||
return
|
||||
|
||||
# Try to decrypt automatically
|
||||
try:
|
||||
plaintext = self.protocol.decrypt_received_message(index)
|
||||
self._log_info(f"Auto-decrypted message: {plaintext}")
|
||||
except Exception as e:
|
||||
self._log_error(f"Failed to auto-decrypt message: {e}")
|
||||
|
||||
def queue_message(self, message: str):
|
||||
"""
|
||||
Add a message to the auto-send queue.
|
||||
|
||||
Args:
|
||||
message: Message text to send
|
||||
"""
|
||||
self.message_queue.put(message)
|
||||
self._log_info(f"Message queued for sending: {message}")
|
||||
|
||||
# If we're in the right state, start sending messages
|
||||
if self.active and self.state == "key_exchange_complete" and self.config.auto_message_enabled:
|
||||
self._process_message_queue()
|
||||
|
||||
def _start_ping_sequence(self):
|
||||
"""Start the ping sequence to discover the peer."""
|
||||
if self.ping_attempts >= self.config.ping_retry_count:
|
||||
self._log_warning(f"Maximum ping attempts ({self.config.ping_retry_count}) reached")
|
||||
self.state = "idle"
|
||||
return
|
||||
|
||||
self.state = "pinging"
|
||||
self.ping_attempts += 1
|
||||
|
||||
self._log_info(f"Sending ping request (attempt {self.ping_attempts}/{self.config.ping_retry_count})")
|
||||
try:
|
||||
self.protocol.send_ping_request(self.config.preferred_cipher)
|
||||
self.last_action_time = time.time()
|
||||
|
||||
# Schedule next ping attempt if needed
|
||||
timer = threading.Timer(
|
||||
self.config.ping_retry_delay,
|
||||
self._check_ping_response
|
||||
)
|
||||
timer.daemon = True
|
||||
timer.start()
|
||||
self.timer_tasks.append(timer)
|
||||
|
||||
except Exception as e:
|
||||
self._log_error(f"Failed to send ping: {e}")
|
||||
|
||||
def _check_ping_response(self):
|
||||
"""Check if we got a ping response, retry if not."""
|
||||
if not self.active or self.state != "pinging":
|
||||
return
|
||||
|
||||
# If we've waited long enough for a response, retry
|
||||
if time.time() - self.last_action_time >= self.config.ping_timeout:
|
||||
self._log_warning("No ping response received, retrying")
|
||||
self._start_ping_sequence()
|
||||
|
||||
def _respond_to_ping(self, index: int):
|
||||
"""
|
||||
Respond to a ping request.
|
||||
|
||||
Args:
|
||||
index: Index of the ping request in the inbound messages
|
||||
"""
|
||||
if not self.active or not self._is_valid_message_index(index):
|
||||
return
|
||||
|
||||
try:
|
||||
answer = 1 if self.config.ping_response_accept else 0
|
||||
self.protocol.respond_to_ping(index, answer)
|
||||
|
||||
if answer == 1:
|
||||
# If we accepted, we should expect a handshake
|
||||
self.state = "accepted_ping"
|
||||
self._ensure_ephemeral_keys()
|
||||
|
||||
# Set a timer to send our handshake if we don't receive one
|
||||
timer = threading.Timer(
|
||||
self.config.handshake_timeout,
|
||||
self._check_handshake_received
|
||||
)
|
||||
timer.daemon = True
|
||||
timer.start()
|
||||
self.timer_tasks.append(timer)
|
||||
self.last_action_time = time.time()
|
||||
|
||||
except Exception as e:
|
||||
self._log_error(f"Failed to respond to ping: {e}")
|
||||
|
||||
def _check_handshake_received(self):
|
||||
"""Check if we've received a handshake after accepting a ping."""
|
||||
if not self.active or self.state != "accepted_ping":
|
||||
return
|
||||
|
||||
# If we've waited long enough and haven't received a handshake, initiate one
|
||||
if time.time() - self.last_action_time >= self.config.handshake_timeout:
|
||||
self._log_warning("No handshake received after accepting ping, initiating handshake")
|
||||
self._start_handshake_sequence()
|
||||
|
||||
def _start_handshake_sequence(self):
|
||||
"""Start the handshake sequence."""
|
||||
if self.handshake_attempts >= self.config.handshake_retry_count:
|
||||
self._log_warning(f"Maximum handshake attempts ({self.config.handshake_retry_count}) reached")
|
||||
self.state = "idle"
|
||||
return
|
||||
|
||||
self.state = "handshake_sent"
|
||||
self.handshake_attempts += 1
|
||||
|
||||
self._log_info(f"Sending handshake (attempt {self.handshake_attempts}/{self.config.handshake_retry_count})")
|
||||
try:
|
||||
self.protocol.send_handshake()
|
||||
self.last_action_time = time.time()
|
||||
|
||||
# Schedule handshake retry check
|
||||
timer = threading.Timer(
|
||||
self.config.handshake_retry_delay,
|
||||
self._check_handshake_response
|
||||
)
|
||||
timer.daemon = True
|
||||
timer.start()
|
||||
self.timer_tasks.append(timer)
|
||||
|
||||
except Exception as e:
|
||||
self._log_error(f"Failed to send handshake: {e}")
|
||||
|
||||
def _check_handshake_response(self):
|
||||
"""Check if we've completed the key exchange, retry handshake if not."""
|
||||
if not self.active or self.state != "handshake_sent":
|
||||
return
|
||||
|
||||
# If we've waited long enough for a response, retry
|
||||
if time.time() - self.last_action_time >= self.config.handshake_timeout:
|
||||
self._log_warning("No handshake response received, retrying")
|
||||
self._start_handshake_sequence()
|
||||
|
||||
def _start_message_sequence(self):
|
||||
"""Start the automated message sending sequence."""
|
||||
if not self.config.auto_message_enabled:
|
||||
return
|
||||
|
||||
self._log_info("Starting automated message sequence")
|
||||
|
||||
# Add the default message if queue is empty
|
||||
if self.message_queue.empty():
|
||||
self.message_queue.put(self.config.message_content)
|
||||
|
||||
# Start processing the queue
|
||||
self._process_message_queue()
|
||||
|
||||
def _process_message_queue(self):
|
||||
"""Process messages in the queue and send them."""
|
||||
if not self.active or self.state != "key_exchange_complete" or not self.config.auto_message_enabled:
|
||||
return
|
||||
|
||||
if not self.message_queue.empty():
|
||||
message = self.message_queue.get()
|
||||
self._log_info(f"Sending queued message: {message}")
|
||||
|
||||
try:
|
||||
self.protocol.send_encrypted_message(message)
|
||||
|
||||
# Schedule next message send
|
||||
timer = threading.Timer(
|
||||
self.config.message_interval,
|
||||
self._process_message_queue
|
||||
)
|
||||
timer.daemon = True
|
||||
timer.start()
|
||||
self.timer_tasks.append(timer)
|
||||
|
||||
except Exception as e:
|
||||
self._log_error(f"Failed to send queued message: {e}")
|
||||
# Put the message back in the queue
|
||||
self.message_queue.put(message)
|
||||
|
||||
def _ensure_ephemeral_keys(self):
|
||||
"""Ensure ephemeral keys are generated if needed."""
|
||||
if not hasattr(self.protocol, 'ephemeral_pubkey') or self.protocol.ephemeral_pubkey is None:
|
||||
self._log_info("Generating ephemeral keys")
|
||||
self.protocol.generate_ephemeral_keys()
|
||||
|
||||
def _is_valid_message_index(self, index: int) -> bool:
|
||||
"""
|
||||
Check if a message index is valid in the protocol's inbound_messages queue.
|
||||
|
||||
Args:
|
||||
index: The index to check
|
||||
|
||||
Returns:
|
||||
bool: True if the index is valid, False otherwise
|
||||
"""
|
||||
if not hasattr(self.protocol, 'inbound_messages'):
|
||||
self._log_error("Protocol has no inbound_messages attribute")
|
||||
return False
|
||||
|
||||
if index < 0 or index >= len(self.protocol.inbound_messages):
|
||||
self._log_error(f"Invalid message index: {index}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
# Helper methods for logging
|
||||
def _log_info(self, message: str):
|
||||
print(f"{BLUE}[AUTO]{RESET} {message}")
|
||||
if hasattr(self, 'verbose_logging') and self.verbose_logging:
|
||||
state_info = f"(state={self.state})"
|
||||
if 'pinging' in self.state and hasattr(self, 'ping_attempts'):
|
||||
state_info += f", attempts={self.ping_attempts}/{self.config.ping_retry_count}"
|
||||
elif 'handshake' in self.state and hasattr(self, 'handshake_attempts'):
|
||||
state_info += f", attempts={self.handshake_attempts}/{self.config.handshake_retry_count}"
|
||||
print(f"{BLUE}[AUTO-DETAIL]{RESET} {state_info}")
|
||||
|
||||
def _log_warning(self, message: str):
|
||||
print(f"{YELLOW}[AUTO-WARN]{RESET} {message}")
|
||||
if hasattr(self, 'verbose_logging') and self.verbose_logging:
|
||||
timer_info = f"Active timers: {len(self.timer_tasks)}"
|
||||
print(f"{YELLOW}[AUTO-WARN-DETAIL]{RESET} {timer_info}")
|
||||
|
||||
def _log_error(self, message: str):
|
||||
print(f"{RED}[AUTO-ERROR]{RESET} {message}")
|
||||
if hasattr(self, 'verbose_logging') and self.verbose_logging:
|
||||
print(f"{RED}[AUTO-ERROR-DETAIL]{RESET} Current state: {self.state}, Active: {self.active}")
|
@ -1,12 +1,53 @@
|
||||
import sys
|
||||
import argparse
|
||||
import shlex
|
||||
from protocol import IcingProtocol
|
||||
|
||||
RED = "\033[91m"
|
||||
GREEN = "\033[92m"
|
||||
YELLOW = "\033[93m"
|
||||
BLUE = "\033[94m"
|
||||
MAGENTA = "\033[95m"
|
||||
CYAN = "\033[96m"
|
||||
RESET = "\033[0m"
|
||||
|
||||
def print_help():
|
||||
"""Display all available commands."""
|
||||
print(f"\n{YELLOW}=== Available Commands ==={RESET}")
|
||||
print(f"\n{CYAN}Basic Protocol Commands:{RESET}")
|
||||
print(" help - Show this help message")
|
||||
print(" peer_id <hex_pubkey> - Set peer identity public key")
|
||||
print(" connect <port> - Connect to a peer at the specified port")
|
||||
print(" show_state - Display current protocol state")
|
||||
print(" exit - Exit the program")
|
||||
|
||||
print(f"\n{CYAN}Manual Protocol Operation:{RESET}")
|
||||
print(" generate_ephemeral_keys - Generate ephemeral ECDH keys")
|
||||
print(" send_ping [cipher] - Send PING request (cipher: 0=AES-GCM, 1=ChaCha20-Poly1305, default: 0)")
|
||||
print(" respond_ping <index> <0|1> - Respond to a PING (0=reject, 1=accept)")
|
||||
print(" send_handshake - Send handshake with ephemeral keys")
|
||||
print(" generate_ecdhe <index> - Process handshake at specified index")
|
||||
print(" derive_hkdf - Derive encryption key using HKDF")
|
||||
print(" send_encrypted <plaintext> - Encrypt and send a message")
|
||||
print(" decrypt <index> - Decrypt received message at index")
|
||||
|
||||
print(f"\n{CYAN}Automatic Mode Commands:{RESET}")
|
||||
print(" auto start - Start automatic mode")
|
||||
print(" auto stop - Stop automatic mode")
|
||||
print(" auto status - Show current auto mode status and configuration")
|
||||
print(" auto config <param> <value> - Configure auto mode parameters")
|
||||
print(" auto config list - Show all configurable parameters")
|
||||
print(" auto message <text> - Queue message for automatic sending")
|
||||
print(" auto passive - Configure as passive peer (responds to pings but doesn't initiate)")
|
||||
print(" auto active - Configure as active peer (initiates protocol)")
|
||||
print(" auto log - Toggle detailed logging for auto mode")
|
||||
|
||||
print(f"\n{CYAN}Debugging Commands:{RESET}")
|
||||
print(" debug_message <index> - Display detailed information about a message in the queue")
|
||||
|
||||
print(f"\n{CYAN}Legacy Commands:{RESET}")
|
||||
print(" auto_responder <on|off> - Enable/disable legacy auto responder (deprecated)")
|
||||
|
||||
|
||||
def main():
|
||||
protocol = IcingProtocol()
|
||||
@ -16,54 +57,27 @@ def main():
|
||||
print("======================================\n" + RESET)
|
||||
print(f"Listening on port: {protocol.local_port}")
|
||||
print(f"Your identity public key (hex): {protocol.identity_pubkey.hex()}")
|
||||
print("\nAvailable commands:")
|
||||
print(" help - Show this help message")
|
||||
print(" peer_id <hex_pubkey> - Set peer identity public key")
|
||||
print(" connect <port> - Connect to a peer at the specified port")
|
||||
print(" generate_ephemeral_keys - Generate ephemeral ECDH keys")
|
||||
print(" send_ping [cipher] - Send PING request (cipher: 0=AES-GCM, 1=ChaCha20-Poly1305, default: 0)")
|
||||
print(" respond_ping <index> <0|1> - Respond to a PING (0=reject, 1=accept)")
|
||||
print(" send_handshake - Send handshake with ephemeral keys")
|
||||
print(" generate_ecdhe <index> - Process handshake at specified index")
|
||||
print(" derive_hkdf - Derive encryption key using HKDF")
|
||||
print(" send_encrypted <plaintext> - Encrypt and send a message")
|
||||
print(" decrypt <index> - Decrypt received message at index")
|
||||
print(" auto_responder <on|off> - Enable/disable automatic responses")
|
||||
print(" show_state - Display current protocol state")
|
||||
print(" exit - Exit the program\n")
|
||||
print_help()
|
||||
|
||||
while True:
|
||||
try:
|
||||
line = input("Cmd> ").strip()
|
||||
line = input(f"{MAGENTA}Cmd>{RESET} ").strip()
|
||||
except EOFError:
|
||||
break
|
||||
if not line:
|
||||
continue
|
||||
|
||||
parts = line.split()
|
||||
parts = shlex.split(line) # Handle quoted arguments properly
|
||||
cmd = parts[0].lower()
|
||||
|
||||
try:
|
||||
# Basic commands
|
||||
if cmd == "exit":
|
||||
protocol.stop()
|
||||
break
|
||||
|
||||
elif cmd == "help":
|
||||
print("\nAvailable commands:")
|
||||
print(" help - Show this help message")
|
||||
print(" peer_id <hex_pubkey> - Set peer identity public key")
|
||||
print(" connect <port> - Connect to a peer at the specified port")
|
||||
print(" generate_ephemeral_keys - Generate ephemeral ECDH keys")
|
||||
print(" send_ping [cipher] - Send PING request (cipher: 0=AES-GCM, 1=ChaCha20-Poly1305, default: 0)")
|
||||
print(" respond_ping <index> <0|1> - Respond to a PING (0=reject, 1=accept)")
|
||||
print(" send_handshake - Send handshake with ephemeral keys")
|
||||
print(" generate_ecdhe <index> - Process handshake at specified index")
|
||||
print(" derive_hkdf - Derive encryption key using HKDF")
|
||||
print(" send_encrypted <plaintext> - Encrypt and send a message")
|
||||
print(" decrypt <index> - Decrypt received message at index")
|
||||
print(" auto_responder <on|off> - Enable/disable automatic responses")
|
||||
print(" show_state - Display current protocol state")
|
||||
print(" exit - Exit the program")
|
||||
print_help()
|
||||
|
||||
elif cmd == "show_state":
|
||||
protocol.show_state()
|
||||
@ -88,7 +102,8 @@ def main():
|
||||
print(f"{RED}[ERROR]{RESET} Invalid port number.")
|
||||
except Exception as e:
|
||||
print(f"{RED}[ERROR]{RESET} Connection failed: {e}")
|
||||
|
||||
|
||||
# Manual protocol operation
|
||||
elif cmd == "generate_ephemeral_keys":
|
||||
protocol.generate_ephemeral_keys()
|
||||
|
||||
@ -103,7 +118,7 @@ def main():
|
||||
cipher = 0
|
||||
except ValueError:
|
||||
print(f"{YELLOW}[WARNING]{RESET} Invalid cipher code. Using AES-GCM (0).")
|
||||
protocol.send_ping_request()
|
||||
protocol.send_ping_request(cipher)
|
||||
|
||||
elif cmd == "send_handshake":
|
||||
protocol.send_handshake()
|
||||
@ -164,6 +179,127 @@ def main():
|
||||
except Exception as e:
|
||||
print(f"{RED}[ERROR]{RESET} Failed to decrypt message: {e}")
|
||||
|
||||
# Debugging commands
|
||||
elif cmd == "debug_message":
|
||||
if len(parts) != 2:
|
||||
print(f"{RED}[ERROR]{RESET} Usage: debug_message <index>")
|
||||
continue
|
||||
try:
|
||||
idx = int(parts[1])
|
||||
protocol.debug_message(idx)
|
||||
except ValueError:
|
||||
print(f"{RED}[ERROR]{RESET} Index must be an integer.")
|
||||
except Exception as e:
|
||||
print(f"{RED}[ERROR]{RESET} Failed to debug message: {e}")
|
||||
|
||||
# Automatic mode commands
|
||||
elif cmd == "auto":
|
||||
if len(parts) < 2:
|
||||
print(f"{RED}[ERROR]{RESET} Usage: auto <command> [options]")
|
||||
print("Available commands: start, stop, status, config, message, passive, active")
|
||||
continue
|
||||
|
||||
subcmd = parts[1].lower()
|
||||
|
||||
if subcmd == "start":
|
||||
protocol.start_auto_mode()
|
||||
print(f"{GREEN}[AUTO]{RESET} Automatic mode started")
|
||||
|
||||
elif subcmd == "stop":
|
||||
protocol.stop_auto_mode()
|
||||
print(f"{GREEN}[AUTO]{RESET} Automatic mode stopped")
|
||||
|
||||
elif subcmd == "status":
|
||||
config = protocol.get_auto_mode_config()
|
||||
print(f"{YELLOW}=== Auto Mode Status ==={RESET}")
|
||||
print(f"Active: {protocol.auto_mode.active}")
|
||||
print(f"State: {protocol.auto_mode.state}")
|
||||
print(f"\n{YELLOW}--- Configuration ---{RESET}")
|
||||
for key, value in vars(config).items():
|
||||
print(f" {key}: {value}")
|
||||
|
||||
elif subcmd == "config":
|
||||
if len(parts) < 3:
|
||||
print(f"{RED}[ERROR]{RESET} Usage: auto config <param> <value> or auto config list")
|
||||
continue
|
||||
|
||||
if parts[2].lower() == "list":
|
||||
config = protocol.get_auto_mode_config()
|
||||
print(f"{YELLOW}=== Auto Mode Configuration Parameters ==={RESET}")
|
||||
for key, value in vars(config).items():
|
||||
print(f" {key} ({type(value).__name__}): {value}")
|
||||
continue
|
||||
|
||||
if len(parts) != 4:
|
||||
print(f"{RED}[ERROR]{RESET} Usage: auto config <param> <value>")
|
||||
continue
|
||||
|
||||
param = parts[2]
|
||||
value_str = parts[3]
|
||||
|
||||
# Convert the string value to the appropriate type
|
||||
config = protocol.get_auto_mode_config()
|
||||
if not hasattr(config, param):
|
||||
print(f"{RED}[ERROR]{RESET} Unknown parameter: {param}")
|
||||
print("Use 'auto config list' to see all available parameters")
|
||||
continue
|
||||
|
||||
current_value = getattr(config, param)
|
||||
try:
|
||||
if isinstance(current_value, bool):
|
||||
if value_str.lower() in ("true", "yes", "on", "1"):
|
||||
value = True
|
||||
elif value_str.lower() in ("false", "no", "off", "0"):
|
||||
value = False
|
||||
else:
|
||||
raise ValueError(f"Boolean value must be true/false/yes/no/on/off/1/0")
|
||||
elif isinstance(current_value, int):
|
||||
value = int(value_str)
|
||||
elif isinstance(current_value, float):
|
||||
value = float(value_str)
|
||||
elif isinstance(current_value, str):
|
||||
value = value_str
|
||||
else:
|
||||
value = value_str # Default to string
|
||||
|
||||
protocol.configure_auto_mode(**{param: value})
|
||||
print(f"{GREEN}[AUTO]{RESET} Set {param} = {value}")
|
||||
|
||||
except ValueError as e:
|
||||
print(f"{RED}[ERROR]{RESET} Invalid value for {param}: {e}")
|
||||
|
||||
elif subcmd == "message":
|
||||
if len(parts) < 3:
|
||||
print(f"{RED}[ERROR]{RESET} Usage: auto message <text>")
|
||||
continue
|
||||
|
||||
message = " ".join(parts[2:])
|
||||
protocol.queue_auto_message(message)
|
||||
print(f"{GREEN}[AUTO]{RESET} Message queued for sending: {message}")
|
||||
|
||||
elif subcmd == "passive":
|
||||
# Configure as passive peer (responds but doesn't initiate)
|
||||
protocol.configure_auto_mode(
|
||||
ping_response_accept=True,
|
||||
ping_auto_initiate=False,
|
||||
active_mode=False
|
||||
)
|
||||
print(f"{GREEN}[AUTO]{RESET} Configured as passive peer")
|
||||
|
||||
elif subcmd == "active":
|
||||
# Configure as active peer (initiates protocol)
|
||||
protocol.configure_auto_mode(
|
||||
ping_response_accept=True,
|
||||
ping_auto_initiate=True,
|
||||
active_mode=True
|
||||
)
|
||||
print(f"{GREEN}[AUTO]{RESET} Configured as active peer")
|
||||
|
||||
else:
|
||||
print(f"{RED}[ERROR]{RESET} Unknown auto mode command: {subcmd}")
|
||||
print("Available commands: start, stop, status, config, message, passive, active")
|
||||
|
||||
# Legacy commands
|
||||
elif cmd == "auto_responder":
|
||||
if len(parts) != 2:
|
||||
print(f"{RED}[ERROR]{RESET} Usage: auto_responder <on|off>")
|
||||
@ -173,6 +309,7 @@ def main():
|
||||
print(f"{RED}[ERROR]{RESET} Value must be 'on' or 'off'.")
|
||||
continue
|
||||
protocol.enable_auto_responder(val == "on")
|
||||
print(f"{YELLOW}[WARNING]{RESET} Using legacy auto responder. Consider using 'auto' commands instead.")
|
||||
|
||||
else:
|
||||
print(f"{RED}[ERROR]{RESET} Unknown command: {cmd}")
|
||||
|
@ -23,6 +23,7 @@ from encryption import (
|
||||
EncryptedMessage, MessageHeader,
|
||||
generate_iv, encrypt_message, decrypt_message
|
||||
)
|
||||
from auto_mode import AutoMode, AutoModeConfig
|
||||
|
||||
# ANSI colors
|
||||
RED = "\033[91m"
|
||||
@ -63,9 +64,13 @@ class IcingProtocol:
|
||||
"ping_received": False,
|
||||
"handshake_sent": False,
|
||||
"handshake_received": False,
|
||||
"key_exchange_complete": False
|
||||
}
|
||||
|
||||
# Auto-responder toggle
|
||||
# Auto mode for automated protocol operation
|
||||
self.auto_mode = AutoMode(self)
|
||||
|
||||
# Legacy auto-responder toggle (kept for backward compatibility)
|
||||
self.auto_responder = False
|
||||
|
||||
# Active connections list
|
||||
@ -96,6 +101,9 @@ class IcingProtocol:
|
||||
def on_new_connection(self, conn: transmission.PeerConnection):
|
||||
print(f"{GREEN}[IcingProtocol]{RESET} New incoming connection.")
|
||||
self.connections.append(conn)
|
||||
|
||||
# Notify auto mode
|
||||
self.auto_mode.handle_connection_established()
|
||||
|
||||
def on_data_received(self, conn: transmission.PeerConnection, data: bytes):
|
||||
bits_count = len(data) * 8
|
||||
@ -130,8 +138,11 @@ class IcingProtocol:
|
||||
}
|
||||
self.inbound_messages.append(msg)
|
||||
|
||||
# Auto-respond if enabled
|
||||
if self.auto_responder:
|
||||
# Handle in auto mode (if active)
|
||||
self.auto_mode.handle_ping_received(index)
|
||||
|
||||
# Legacy auto-responder (for backward compatibility)
|
||||
if self.auto_responder and not self.auto_mode.active:
|
||||
timer = threading.Timer(2.0, self._auto_respond_ping, args=[index])
|
||||
timer.daemon = True
|
||||
timer.start()
|
||||
@ -152,6 +163,9 @@ class IcingProtocol:
|
||||
"connection": conn
|
||||
}
|
||||
self.inbound_messages.append(msg)
|
||||
|
||||
# Notify auto mode (if active)
|
||||
self.auto_mode.handle_ping_response_received(ping_response.answer == 1)
|
||||
return
|
||||
|
||||
# HANDSHAKE message (168 bytes)
|
||||
@ -168,8 +182,11 @@ class IcingProtocol:
|
||||
}
|
||||
self.inbound_messages.append(msg)
|
||||
|
||||
# Auto-respond if enabled
|
||||
if self.auto_responder:
|
||||
# Notify auto mode (if active)
|
||||
self.auto_mode.handle_handshake_received(index)
|
||||
|
||||
# Legacy auto-responder
|
||||
if self.auto_responder and not self.auto_mode.active:
|
||||
timer = threading.Timer(2.0, self._auto_respond_handshake, args=[index])
|
||||
timer.daemon = True
|
||||
timer.start()
|
||||
@ -198,6 +215,9 @@ class IcingProtocol:
|
||||
}
|
||||
self.inbound_messages.append(msg)
|
||||
print(f"{YELLOW}[NOTICE]{RESET} Stored inbound ENCRYPTED_MESSAGE at index={index}.")
|
||||
|
||||
# Notify auto mode
|
||||
self.auto_mode.handle_encrypted_received(index)
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"{RED}[ERROR]{RESET} Failed to parse message header: {e}")
|
||||
@ -269,10 +289,12 @@ class IcingProtocol:
|
||||
)
|
||||
derived_key = hkdf.derive(ikm)
|
||||
self.hkdf_key = derived_key.hex()
|
||||
self.state["key_exchange_complete"] = True
|
||||
print(f"{GREEN}[HKDF]{RESET} Derived HKDF key: {self.hkdf_key}")
|
||||
return True
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Auto-responder helpers
|
||||
# Legacy Auto-responder helpers (kept for backward compatibility)
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def _auto_respond_ping(self, index: int):
|
||||
@ -306,6 +328,140 @@ class IcingProtocol:
|
||||
# 4) Show final state
|
||||
self.show_state()
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Public Methods for Auto Mode Management
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def start_auto_mode(self):
|
||||
"""Start the automatic protocol operation mode."""
|
||||
self.auto_mode.start()
|
||||
|
||||
def stop_auto_mode(self):
|
||||
"""Stop the automatic protocol operation mode."""
|
||||
self.auto_mode.stop()
|
||||
|
||||
def configure_auto_mode(self, **kwargs):
|
||||
"""
|
||||
Configure the automatic mode parameters.
|
||||
|
||||
Args:
|
||||
**kwargs: Configuration parameters to set. Supported parameters:
|
||||
- ping_response_accept: bool, whether to accept incoming pings
|
||||
- ping_auto_initiate: bool, whether to initiate pings on connection
|
||||
- ping_retry_count: int, number of ping retries
|
||||
- ping_retry_delay: float, seconds between ping retries
|
||||
- ping_timeout: float, seconds to wait for ping response
|
||||
- preferred_cipher: int, preferred cipher (0=AES-GCM, 1=ChaCha20-Poly1305)
|
||||
- handshake_retry_count: int, number of handshake retries
|
||||
- handshake_retry_delay: float, seconds between handshake retries
|
||||
- handshake_timeout: float, seconds to wait for handshake
|
||||
- auto_message_enabled: bool, whether to auto-send messages
|
||||
- message_interval: float, seconds between auto messages
|
||||
- message_content: str, default message content
|
||||
- active_mode: bool, whether to actively initiate protocol
|
||||
"""
|
||||
for key, value in kwargs.items():
|
||||
if hasattr(self.auto_mode.config, key):
|
||||
setattr(self.auto_mode.config, key, value)
|
||||
print(f"{BLUE}[CONFIG]{RESET} Set auto mode {key} = {value}")
|
||||
else:
|
||||
print(f"{RED}[ERROR]{RESET} Unknown auto mode configuration parameter: {key}")
|
||||
|
||||
def get_auto_mode_config(self):
|
||||
"""Return the current auto mode configuration."""
|
||||
return self.auto_mode.config
|
||||
|
||||
def queue_auto_message(self, message: str):
|
||||
"""
|
||||
Add a message to the auto-send queue.
|
||||
|
||||
Args:
|
||||
message: Message text to send
|
||||
"""
|
||||
self.auto_mode.queue_message(message)
|
||||
|
||||
def toggle_auto_mode_logging(self):
|
||||
"""
|
||||
Toggle detailed logging for auto mode.
|
||||
When enabled, will show more information about state transitions and decision making.
|
||||
"""
|
||||
if not hasattr(self.auto_mode, 'verbose_logging'):
|
||||
self.auto_mode.verbose_logging = True
|
||||
else:
|
||||
self.auto_mode.verbose_logging = not self.auto_mode.verbose_logging
|
||||
|
||||
status = "enabled" if self.auto_mode.verbose_logging else "disabled"
|
||||
print(f"{BLUE}[AUTO-LOG]{RESET} Detailed logging {status}")
|
||||
|
||||
def debug_message(self, index: int):
|
||||
"""
|
||||
Debug a message in the inbound message queue.
|
||||
Prints detailed information about the message.
|
||||
|
||||
Args:
|
||||
index: The index of the message in the inbound_messages queue
|
||||
"""
|
||||
if index < 0 or index >= len(self.inbound_messages):
|
||||
print(f"{RED}[ERROR]{RESET} Invalid message index {index}")
|
||||
return
|
||||
|
||||
msg = self.inbound_messages[index]
|
||||
print(f"\n{YELLOW}=== Message Debug [{index}] ==={RESET}")
|
||||
print(f"Type: {msg['type']}")
|
||||
print(f"Length: {len(msg['raw'])} bytes = {len(msg['raw'])*8} bits")
|
||||
print(f"Raw data: {msg['raw'].hex()}")
|
||||
|
||||
if msg['parsed'] is not None:
|
||||
print(f"\n{YELLOW}--- Parsed Data ---{RESET}")
|
||||
if msg['type'] == 'PING_REQUEST':
|
||||
ping = msg['parsed']
|
||||
print(f"Version: {ping.version}")
|
||||
print(f"Cipher: {ping.cipher} ({'AES-256-GCM' if ping.cipher == 0 else 'ChaCha20-Poly1305' if ping.cipher == 1 else 'Unknown'})")
|
||||
print(f"Session nonce: {ping.session_nonce.hex()}")
|
||||
print(f"CRC32: {ping.crc32:08x}")
|
||||
|
||||
elif msg['type'] == 'PING_RESPONSE':
|
||||
resp = msg['parsed']
|
||||
print(f"Version: {resp.version}")
|
||||
print(f"Cipher: {resp.cipher} ({'AES-256-GCM' if resp.cipher == 0 else 'ChaCha20-Poly1305' if resp.cipher == 1 else 'Unknown'})")
|
||||
print(f"Answer: {resp.answer} ({'Accept' if resp.answer == 1 else 'Reject'})")
|
||||
print(f"CRC32: {resp.crc32:08x}")
|
||||
|
||||
elif msg['type'] == 'HANDSHAKE':
|
||||
hs = msg['parsed']
|
||||
print(f"Ephemeral pubkey: {hs.ephemeral_pubkey.hex()[:16]}...")
|
||||
print(f"Ephemeral signature: {hs.ephemeral_signature.hex()[:16]}...")
|
||||
print(f"PFS hash: {hs.pfs_hash.hex()[:16]}...")
|
||||
print(f"Timestamp: {hs.timestamp}")
|
||||
print(f"CRC32: {hs.crc32:08x}")
|
||||
|
||||
elif msg['type'] == 'ENCRYPTED_MESSAGE':
|
||||
header = msg['parsed']
|
||||
print(f"Flag: 0x{header.flag:04x}")
|
||||
print(f"Data length: {header.data_len} bytes")
|
||||
print(f"Retry: {header.retry}")
|
||||
print(f"Connection status: {header.connection_status} ({'CRC included' if header.connection_status & 0x01 else 'No CRC'})")
|
||||
print(f"IV: {header.iv.hex()}")
|
||||
|
||||
# Calculate expected message size
|
||||
expected_len = 18 + header.data_len + 16 # Header + payload + tag
|
||||
if header.connection_status & 0x01:
|
||||
expected_len += 4 # Add CRC
|
||||
|
||||
print(f"Expected total length: {expected_len} bytes")
|
||||
print(f"Actual length: {len(msg['raw'])} bytes")
|
||||
|
||||
# If we have a key, try to decrypt
|
||||
if self.hkdf_key:
|
||||
print("\nAttempting decryption...")
|
||||
try:
|
||||
key = bytes.fromhex(self.hkdf_key)
|
||||
plaintext = decrypt_message(msg['raw'], key, self.cipher_type)
|
||||
print(f"Decrypted: {plaintext.decode('utf-8')}")
|
||||
except Exception as e:
|
||||
print(f"Decryption failed: {e}")
|
||||
print()
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Public Methods
|
||||
# -------------------------------------------------------------------------
|
||||
@ -314,6 +470,9 @@ class IcingProtocol:
|
||||
conn = transmission.connect_to_peer("127.0.0.1", port, self.on_data_received)
|
||||
self.connections.append(conn)
|
||||
print(f"{GREEN}[IcingProtocol]{RESET} Outgoing connection to port {port} established.")
|
||||
|
||||
# Notify auto mode
|
||||
self.auto_mode.handle_connection_established()
|
||||
|
||||
def set_peer_identity(self, peer_pubkey_hex: str):
|
||||
pubkey_bytes = bytes.fromhex(peer_pubkey_hex)
|
||||
@ -326,13 +485,24 @@ class IcingProtocol:
|
||||
print(f"{GREEN}[IcingProtocol]{RESET} Generated ephemeral key pair: pubkey={self.ephemeral_pubkey.hex()[:16]}...")
|
||||
|
||||
# Send PING (session discovery and cipher negotiation)
|
||||
def send_ping_request(self):
|
||||
def send_ping_request(self, cipher_type=0):
|
||||
"""
|
||||
Send a ping request to the first connected peer.
|
||||
|
||||
Args:
|
||||
cipher_type: Preferred cipher type (0 = AES-256-GCM, 1 = ChaCha20-Poly1305)
|
||||
"""
|
||||
if not self.connections:
|
||||
print(f"{RED}[ERROR]{RESET} No active connections.")
|
||||
return
|
||||
return False
|
||||
|
||||
# Validate cipher type
|
||||
if cipher_type not in (0, 1):
|
||||
print(f"{YELLOW}[WARNING]{RESET} Invalid cipher type {cipher_type}, defaulting to AES-256-GCM (0)")
|
||||
cipher_type = 0
|
||||
|
||||
# Create ping request with our default cipher preference (AES-256-GCM = 0)
|
||||
ping_request = PingRequest(version=0, cipher=0)
|
||||
# Create ping request with specified cipher
|
||||
ping_request = PingRequest(version=0, cipher=cipher_type)
|
||||
|
||||
# Store session nonce if not already set
|
||||
if self.session_nonce is None:
|
||||
@ -343,6 +513,7 @@ class IcingProtocol:
|
||||
pkt = ping_request.serialize()
|
||||
self._send_packet(self.connections[0], pkt, "PING_REQUEST")
|
||||
self.state["ping_sent"] = True
|
||||
return True
|
||||
|
||||
def send_handshake(self):
|
||||
"""
|
||||
@ -355,13 +526,13 @@ class IcingProtocol:
|
||||
"""
|
||||
if not self.connections:
|
||||
print(f"{RED}[ERROR]{RESET} No active connections.")
|
||||
return
|
||||
return False
|
||||
if not self.ephemeral_privkey or not self.ephemeral_pubkey:
|
||||
print(f"{RED}[ERROR]{RESET} Ephemeral keys not generated.")
|
||||
return
|
||||
return False
|
||||
if self.peer_identity_pubkey_bytes is None:
|
||||
print(f"{RED}[ERROR]{RESET} Peer identity not set; needed for PFS tracking.")
|
||||
return
|
||||
return False
|
||||
|
||||
# 1) Sign ephemeral_pubkey using identity key
|
||||
sig_der = sign_data(self.identity_privkey, self.ephemeral_pubkey)
|
||||
@ -383,10 +554,15 @@ class IcingProtocol:
|
||||
pkt = handshake.serialize()
|
||||
self._send_packet(self.connections[0], pkt, "HANDSHAKE")
|
||||
self.state["handshake_sent"] = True
|
||||
return True
|
||||
|
||||
def enable_auto_responder(self, enable: bool):
|
||||
"""
|
||||
Legacy method for enabling/disabling auto responder.
|
||||
For new code, use start_auto_mode() and stop_auto_mode() instead.
|
||||
"""
|
||||
self.auto_responder = enable
|
||||
print(f"{BLUE}[AUTO]{RESET} Auto responder set to {enable}.")
|
||||
print(f"{YELLOW}[LEGACY]{RESET} Auto responder set to {enable}. Consider using auto_mode instead.")
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Manual Responses
|
||||
@ -399,11 +575,11 @@ class IcingProtocol:
|
||||
"""
|
||||
if index < 0 or index >= len(self.inbound_messages):
|
||||
print(f"{RED}[ERROR]{RESET} Invalid index {index}.")
|
||||
return
|
||||
return False
|
||||
msg = self.inbound_messages[index]
|
||||
if msg["type"] != "PING_REQUEST":
|
||||
print(f"{RED}[ERROR]{RESET} inbound_messages[{index}] is not a PING_REQUEST.")
|
||||
return
|
||||
return False
|
||||
|
||||
ping_request = msg["parsed"]
|
||||
version = ping_request.version
|
||||
@ -424,6 +600,7 @@ class IcingProtocol:
|
||||
resp = ping_response.serialize()
|
||||
self._send_packet(conn, resp, "PING_RESPONSE")
|
||||
print(f"{BLUE}[MANUAL]{RESET} Responded to ping with answer={answer}.")
|
||||
return True
|
||||
|
||||
def generate_ecdhe(self, index: int):
|
||||
"""
|
||||
@ -434,11 +611,11 @@ class IcingProtocol:
|
||||
"""
|
||||
if index < 0 or index >= len(self.inbound_messages):
|
||||
print(f"{RED}[ERROR]{RESET} Invalid index {index}.")
|
||||
return
|
||||
return False
|
||||
msg = self.inbound_messages[index]
|
||||
if msg["type"] != "HANDSHAKE":
|
||||
print(f"{RED}[ERROR]{RESET} inbound_messages[{index}] is not a HANDSHAKE.")
|
||||
return
|
||||
return False
|
||||
|
||||
handshake = msg["parsed"]
|
||||
|
||||
@ -450,13 +627,13 @@ class IcingProtocol:
|
||||
ok = verify_signature(self.peer_identity_pubkey_obj, sig_der, handshake.ephemeral_pubkey)
|
||||
if not ok:
|
||||
print(f"{RED}[ERROR]{RESET} Ephemeral signature invalid.")
|
||||
return
|
||||
return False
|
||||
print(f"{GREEN}[OK]{RESET} Ephemeral signature verified.")
|
||||
|
||||
# Check if we have ephemeral keys
|
||||
if not self.ephemeral_privkey:
|
||||
print(f"{YELLOW}[WARN]{RESET} No ephemeral_privkey available, cannot compute shared secret.")
|
||||
return
|
||||
return False
|
||||
|
||||
# Compute ECDH shared secret
|
||||
shared = compute_ecdh_shared_key(self.ephemeral_privkey, handshake.ephemeral_pubkey)
|
||||
@ -467,6 +644,7 @@ class IcingProtocol:
|
||||
old_session, _ = self.pfs_history.get(self.peer_identity_pubkey_bytes, (-1, ""))
|
||||
new_session = 1 if old_session < 0 else old_session + 1
|
||||
self.pfs_history[self.peer_identity_pubkey_bytes] = (new_session, self.shared_secret)
|
||||
return True
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Utility
|
||||
@ -516,7 +694,9 @@ class IcingProtocol:
|
||||
for k, v in self.state.items():
|
||||
print(f" {k}: {v}")
|
||||
|
||||
print("\nAuto Responder:", self.auto_responder)
|
||||
print("\nAuto Mode Active:", self.auto_mode.active)
|
||||
print("Auto Mode State:", self.auto_mode.state)
|
||||
print("Legacy Auto Responder:", self.auto_responder)
|
||||
|
||||
print("\nActive Connections:")
|
||||
for i, c in enumerate(self.connections):
|
||||
@ -528,14 +708,24 @@ class IcingProtocol:
|
||||
print()
|
||||
|
||||
def stop(self):
|
||||
"""Stop the protocol and clean up resources."""
|
||||
# Stop auto mode first
|
||||
self.auto_mode.stop()
|
||||
|
||||
# Stop server listener
|
||||
self.server_listener.stop()
|
||||
|
||||
# Close all connections
|
||||
for c in self.connections:
|
||||
c.close()
|
||||
self.connections.clear()
|
||||
self.inbound_messages.clear()
|
||||
print(f"{RED}[STOP]{RESET} Protocol stopped.")
|
||||
|
||||
# New method: Send an encrypted message over the first active connection.
|
||||
# -------------------------------------------------------------------------
|
||||
# Encrypted Messaging
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def send_encrypted_message(self, plaintext: str):
|
||||
"""
|
||||
Encrypts and sends a message using the derived HKDF key and negotiated cipher.
|
||||
@ -546,10 +736,10 @@ class IcingProtocol:
|
||||
"""
|
||||
if not self.connections:
|
||||
print(f"{RED}[ERROR]{RESET} No active connections.")
|
||||
return
|
||||
return False
|
||||
if not self.hkdf_key:
|
||||
print(f"{RED}[ERROR]{RESET} No HKDF key derived. Cannot encrypt message.")
|
||||
return
|
||||
return False
|
||||
|
||||
# Get the encryption key
|
||||
key = bytes.fromhex(self.hkdf_key)
|
||||
@ -582,27 +772,27 @@ class IcingProtocol:
|
||||
# Send the encrypted message
|
||||
self._send_packet(self.connections[0], encrypted, "ENCRYPTED_MESSAGE")
|
||||
print(f"{GREEN}[SEND_ENCRYPTED]{RESET} Encrypted message sent.")
|
||||
return True
|
||||
|
||||
# New method: Decrypt an encrypted message from the inbound queue.
|
||||
def decrypt_received_message(self, index: int):
|
||||
"""
|
||||
Decrypt a received encrypted message using the HKDF key and negotiated cipher.
|
||||
"""
|
||||
if index < 0 or index >= len(self.inbound_messages):
|
||||
print(f"{RED}[ERROR]{RESET} Invalid message index.")
|
||||
return
|
||||
return None
|
||||
|
||||
msg = self.inbound_messages[index]
|
||||
if msg["type"] != "ENCRYPTED_MESSAGE":
|
||||
print(f"{RED}[ERROR]{RESET} Message at index {index} is not an ENCRYPTED_MESSAGE.")
|
||||
return
|
||||
return None
|
||||
|
||||
# Get the encrypted message
|
||||
encrypted = msg["raw"]
|
||||
|
||||
if not self.hkdf_key:
|
||||
print(f"{RED}[ERROR]{RESET} No HKDF key derived. Cannot decrypt message.")
|
||||
return
|
||||
return None
|
||||
|
||||
# Get the encryption key
|
||||
key = bytes.fromhex(self.hkdf_key)
|
||||
|
Loading…
Reference in New Issue
Block a user