Warning

This documentation is actively being updated as the project evolves and may not be complete in all areas.

Adapter Classes and Architecture

Adapter Architecture

Jumpstarter adapters transform connections established by drivers into different forms or interfaces. The architecture follows a pattern with these key components:

  • Adapter Base - Adapters typically follow a context manager pattern using Python’s with statement for resource management. Each adapter takes a driver client as input and transforms its connection.

  • Connection Transformation - Adapters create a new interface on top of an existing driver connection, such as forwarding ports, providing web interfaces, or offering terminal-like access.

  • Resource Lifecycle - Adapters handle proper setup and teardown of resources, ensuring connections are properly established and cleaned up.

Unlike drivers, which have a strict client/server architecture, adapters operate entirely on the client side and transform existing connections rather than establishing new ones directly with hardware or virtual devices.

Adapter Types

Different types of adapters serve different needs:

  • Port Forwarding Adapters - Convert network connections to local ports or sockets

  • Interactive Adapters - Provide interactive shells or console-like interfaces

  • Protocol Adapters - Transform connections to use different protocols (e.g., SSH, VNC)

  • UI Adapters - Create user interfaces for interacting with devices (e.g., web-based VNC)

Implementation Patterns

Adapters typically implement the context manager protocol (__enter__ and __exit__) to ensure proper resource management. The general pattern is:

  1. Initialize with a driver client reference

  2. Set up the transformed connection in __enter__

  3. Return the appropriate interface (URL, address, interactive object)

  4. Clean up resources in __exit__

This allows adapters to be used in with statements for clean, deterministic resource handling.

Example Implementation

from contextlib import contextmanager
import socket
import threading
from typing import Tuple, Any

class TcpPortforwardAdapter:
    """
    Adapter that forwards a remote TCP port to a local TCP port.
    
    Args:
        client: A network driver client that provides a connection
        local_host: Host to bind to (default: 127.0.0.1)
        local_port: Port to bind to (default: 0, which selects a random port)
    
    Returns:
        A tuple of (host, port) when used as a context manager
    """
    def __init__(self, client, local_host="127.0.0.1", local_port=0):
        self.client = client
        self.local_host = local_host
        self.local_port = local_port
        self._server = None
        self._thread = None
    
    def __enter__(self) -> Tuple[str, int]:
        # Create a socket server
        self._server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._server.bind((self.local_host, self.local_port))
        self._server.listen(5)
        
        # Get the actual port (if we used port 0)
        self.local_host, self.local_port = self._server.getsockname()
        
        # Start a thread to handle connections
        self._thread = threading.Thread(target=self._handle_connections, daemon=True)
        self._thread.start()
        
        return (self.local_host, self.local_port)
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        if self._server:
            self._server.close()
            self._server = None
        
        # Thread will exit because it's a daemon
        self._thread = None
    
    def _handle_connections(self):
        while True:
            try:
                client_socket, _ = self._server.accept()
                # For each connection, establish a connection to the remote
                # and set up bidirectional forwarding
                remote_conn = self.client.connect()
                self._start_forwarding(client_socket, remote_conn)
            except Exception:
                # Server was closed or other error
                break
    
    def _start_forwarding(self, local_socket, remote_conn):
        # Set up bidirectional forwarding between local_socket and remote_conn
        # Typically done with two threads, one for each direction
        # Implementation details depend on the specific driver client interface
        pass


# Example usage:
def example_usage():
    # Assuming 'client' is a network driver client
    with TcpPortforwardAdapter(client, local_port=8080) as (host, port):
        print(f"Service available at {host}:{port}")
        # The service is now accessible at the local address
        # while this context is active

Advanced Usage

Adapters can be composed and extended for more complex scenarios:

  • Chaining adapters: Use the output of one adapter as the input to another

  • Custom adapters: Create specialized adapters for specific hardware or software interfaces

  • Extended functionality: Add logging, monitoring, or security features on top of base adapters

Best Practices

When working with adapters:

  1. Always use context managers (with statements) to ensure proper resource cleanup

  2. Consider security implications when forwarding ports or providing network access

  3. Implement proper error handling and retries for robust connections

  4. Use appropriate timeouts to prevent hanging connections

  5. Consider performance implications for long-running connections or high-throughput scenarios