import socket
import threading
import time
import sys

def parse_speed_limit(speed_limit):
    """Parse the speed limit string and return bytes per second."""
    if speed_limit[-1].upper() not in {'K', 'M', 'G'}:
        raise ValueError(f"Invalid speed limit unit: {speed_limit[-1]}")
    count = int(speed_limit[:-1])
    units = {'K': 1024, 'M': 1024**2, 'G': 1024**3}
    return count * units[speed_limit[-1].upper()]

# Dictionary to track clients and their data transfer statistics
clients_stats = {}
clients_lock = threading.Lock()

def forward(source, destination, speed_limit_bps, client_id):
    """Forward data from source to destination at a limited speed, tracking stats."""
    buffer_size = 4096
    total_sent = 0
    total_received = 0

    while True:
        # Receive data from the source
        data = source.recv(buffer_size)
        if not data:
            break  # Exit if there's no data (connection closed)

        # Send data to the destination
        destination.sendall(data)
        total_sent += len(data)

        # Update the received data for the stats (from destination point of view)
        total_received += len(data)

        # Control the speed by sleeping as necessary
        if speed_limit_bps > 0:
            # Calculate current bytes per second
            elapsed_time = time.time() - start_time
            if elapsed_time > 0:
                current_bps = total_sent / elapsed_time
                if current_bps > speed_limit_bps:
                    excess_time = (total_sent / speed_limit_bps) - elapsed_time
                    if excess_time > 0:
                        time.sleep(excess_time)

    # Clean up on exit
    source.close()
    destination.close()

    # Update statistics
    with clients_lock:
        clients_stats[client_id]['sent'] += total_sent
        clients_stats[client_id]['received'] += total_received

def start_forwarder(local_host, local_port, remote_host, remote_port, speed_limit):
    speed_limit_bps = parse_speed_limit(speed_limit)

    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # Allow reuse of local address
    server.bind((local_host, local_port))
    server.listen(1)
    print(f"Listening on {local_host}:{local_port}")

    try:
        while True:
            try:
                client_socket, client_address = server.accept()
                print(f"Accepted connection from client {client_address}")

                # Initialize stats for the client
                client_id = f"{client_address}"
                with clients_lock:
                    if client_id not in clients_stats:
                        clients_stats[client_id] = {'sent': 0, 'received': 0}

                remote_socket = socket.create_connection((remote_host, remote_port))
                print(f"Connected to remote server {remote_host}:{remote_port}")

                threading.Thread(target=forward, args=(client_socket, remote_socket, speed_limit_bps, client_id)).start()
                threading.Thread(target=forward, args=(remote_socket, client_socket, speed_limit_bps, client_id)).start()
            except KeyboardInterrupt:
                print("Shutting down the server...")
                break
            except Exception as e:
                print(f"Error accepting connections: {e}")

    finally:
        server.close()  # Ensure server socket is closed on exit

def update_clients_stats():
    while True:
        time.sleep(5)  # Update interval
        with clients_lock:
            print("\nClient Statistics (MB):")
            print("Client Address       | Sent    | Received")
            print("---------------------|---------|---------")
            for client_id, stats in clients_stats.items():
                sent_mb = stats['sent'] / (1024 * 1024)
                received_mb = stats['received'] / (1024 * 1024)
                print(f"{client_id:<20} | {sent_mb:7.2f} | {received_mb:7.2f}")

def main():
    if len(sys.argv) != 6:
        print('Usage: tcp_forwarder.py <listen_ip> <listen_port> <target_ip> <target_port> <speed_limit>')
        exit(1)

    source_ip = sys.argv[1]
    source_port = int(sys.argv[2])
    target_ip = sys.argv[3]
    target_port = int(sys.argv[4])
    speed_limit = sys.argv[5]
    
    # Start the stats updating thread
    threading.Thread(target=update_clients_stats, daemon=True).start()

    start_forwarder(source_ip, source_port, target_ip, target_port, speed_limit)

if __name__ == "__main__":
    main()


    elapsed_time = time.time() - start_time
                                 ^^^^^^^^^^
NameError: name 'start_time' is not defined