ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
Application: Client

💻 Client application for connecting to servers and streaming media More...

Modules

 Audio Processing
 🔊 Audio capture, playback, and sample processing
 
 Media Capture
 📹 Webcam video capture and transmission to server
 
 Connection Management
 🔗 Server connection establishment, packet transmission, and state tracking
 
 Cryptographic Operations
 🔐 Client-side cryptographic handshake and key management
 
 Terminal Display
 🖥️ Terminal rendering, TTY management, and frame output
 
 Connection Keepalive
 💓 Ping/pong keepalive and timeout detection
 
 Client Main Entry Point
 🚀 Main entry point and lifecycle orchestration for the client application
 
 Client Handler
 📡 Client-side packet processing and data reception thread
 

Detailed Description

💻 Client application for connecting to servers and streaming media

Client connection management, media capture, display rendering, and reconnection logic.

Overview of Client (src/client/*)

Overview

The ascii-chat client is a real-time audio-video communication terminal application that captures local media, encrypts, and transmits to the server, while rendering remote participants as ASCII art in the terminal.

The client executes multiple concurrent operations: webcam capture, audio capture/playback, encryption, network transmission, frame reception, and terminal rendering. These operations are coordinated through a multithreaded architecture that maintains responsiveness while handling network interruptions and media synchronization.

The client automatically manages connection lifecycle including authentication, connection establishment, automatic reconnection with exponential backoff on network interruption, and graceful shutdown. Error conditions such as network timeouts, packet loss, and device failures are handled without requiring user intervention.

Implementation: src/client/*.c, src/client/*.h

Key capabilities:

  • Automatic connection management: Exponential backoff reconnection prevents server spam
  • Cross-platform video capture: Supports system webcams on macOS, Linux, Windows; test patterns available
  • Audio I/O: PortAudio for capture and playback with jitter buffering
  • Real-time frame rendering: Terminal rendering pipeline for immediate ASCII display
  • Encryption by default: End-to-end encryption using X25519/XSalsa20-Poly1305
  • SSH key authentication: Ed25519 key-based authentication from existing SSH infrastructure
  • Connection persistence: Keepalive mechanism prevents idle disconnects
  • Network resilience: Automatic reconnection on transport failures
  • Snapshot mode: Single-frame capture for testing

Architecture

Threading Model

The client uses a modular threading architecture:

  • Main Thread: Connection management, reconnection logic, event coordination
  • Data Reception Thread (protocol.c): Handles incoming packets from server
  • Ping Thread (keepalive.c): Sends keepalive pings every 5 seconds
  • Webcam Capture Thread (capture.c): Captures and transmits video frames
  • Audio Capture Thread (audio.c): Captures and transmits audio data (optional)

Thread Lifecycle:

// Connection established
protocol_start_connection(); // Start data reception thread
keepalive_start_thread(); // Start ping thread
capture_start_thread(); // Start webcam capture thread
if (opt_audio_enabled) {
audio_start_thread(); // Start audio capture thread
}
// Connection lost or shutdown
void audio_stop_thread()
Stop audio capture thread.
int audio_start_thread()
Start audio capture thread.
int capture_start_thread()
Start capture thread.
void capture_stop_thread()
Stop capture thread.
int keepalive_start_thread()
Start keepalive/ping thread.
Definition keepalive.c:236
void keepalive_stop_thread()
Stop keepalive/ping thread.
Definition keepalive.c:269
void protocol_stop_connection()
Stop protocol connection handling.
int protocol_start_connection()
Start protocol connection handling.

ACDS Session Discovery

Instead of connecting via IP addresses, the client can use human-friendly session strings:

# Connect using session string (via ACDS lookup)
ascii-chat client happy-sunset-ocean
# Connect with explicit ACDS server
ascii-chat client happy-sunset-ocean --acds-server acds.example.com

ACDS Connection Flow:

  1. Client sends ACIP_SESSION_LOOKUP to ACDS server
  2. ACDS returns session info (capabilities, password requirement, etc.)
  3. Client sends ACIP_SESSION_JOIN with credentials (if required)
  4. ACDS returns server connection info:
    • Direct TCP address/port (if server allows IP exposure)
    • TURN credentials and STUN servers (for WebRTC fallback)
  5. Client attempts connection in priority order:
    • Direct TCP (lowest latency, if available)
    • WebRTC P2P via ICE/STUN (when direct fails)
    • TURN relay (always succeeds, higher latency)

Benefits of ACDS:

  • No need to share IP addresses or configure port forwarding
  • Works behind NATs and firewalls automatically
  • Human-friendly session strings (e.g., "purple-mountain-lake")
  • Graceful fallback from direct TCP to WebRTC to TURN relay
See also
ACDS Overview for complete discovery service documentation

Reconnection Logic

The client implements exponential backoff with jitter for reconnection:

Retry Strategy:

  • Initial delay: 100ms
  • Linear growth: delay = 100ms + (200ms * (attempt - 1))
  • Maximum delay: 5 seconds
  • Jitter: Small random component to prevent thundering herd

Reconnection Flow:

int reconnect_attempt = 1;
while (!should_exit()) {
if (server_connection_establish(...) == 0) {
// Connection successful
reconnect_attempt = 1;
break;
}
// Linear backoff with cap at 5 seconds
int delay_ms = 100 + (200 * (reconnect_attempt - 1));
if (delay_ms > 5000) delay_ms = 5000;
platform_sleep_usec(delay_ms * 1000);
reconnect_attempt++;
}
int server_connection_establish(const char *address, int port, int reconnect_attempt, bool first_connection, bool has_ever_connected)
Establish connection to ascii-chat server.
bool should_exit(void)
Definition misc.c:131

Connection Loss Detection:

  • Thread exit detection (data reception thread died)
  • Socket error detection (network errors during packet send)
  • Keepalive timeout (no pong response for 30 seconds)

Client Modules

Main Entry Point

File: src/client/main.c, src/client/main.h

Responsibilities:

  • Command-line argument parsing
  • Platform and subsystem initialization
  • Connection establishment and reconnection orchestration
  • Signal handling (Ctrl+C for graceful shutdown)
  • Main event loop and thread coordination
  • Cleanup and resource deallocation

Global State:

  • g_should_exit: Atomic boolean for shutdown coordination
  • g_send_mutex: Global mutex protecting packet transmission

Error Handling:

  • Returns 0 on success
  • Returns 1 on configuration/initialization errors
  • Returns 2 on connection errors (after max retries)
  • Returns 130 on SIGINT (Ctrl+C)

Server Connection Management

File: src/client/server.c, src/client/server.h

Responsibilities:

  • TCP socket creation and connection establishment
  • Address resolution (IPv4/IPv6)
  • Cryptographic handshake coordination
  • Thread-safe packet transmission (send_packet_to_server)
  • Connection state tracking
  • Socket cleanup and shutdown

Connection State:

  • g_connection_active: Atomic boolean indicating active connection
  • g_connection_lost: Atomic boolean indicating detected connection loss
  • g_sockfd: Socket file descriptor
  • g_client_id: Server-assigned client ID
  • g_server_ip: Resolved server IP address

Thread Safety:

  • All packet sends protected by g_send_mutex
  • Atomic operations for connection state flags
  • Socket FD checked before every send operation

Key Functions:

// Establish connection to server
int server_connection_establish(const char *address, int port, int reconnect_attempt,
bool first_connection, bool has_ever_connected);
// Send packet to server (thread-safe)
int send_packet_to_server(packet_type_t type, const void *data, size_t data_len,
uint32_t client_id);
// Check connection status
bool server_connection_was_lost();
// Close connection gracefully
void server_connection_close()
Close the server connection gracefully.
bool server_connection_is_active()
Check if server connection is currently active.
unsigned int uint32_t
Definition common.h:58
packet_type_t
Network protocol packet type enumeration.
Definition packet.h:286

Protocol Handler

File: src/client/protocol.c, src/client/protocol.h

Responsibilities:

  • Data reception thread management
  • Incoming packet processing
  • Packet type dispatching
  • Frame rendering coordination
  • Connection loss detection

Packet Types Handled:

  • PACKET_TYPE_ASCII_FRAME: ASCII frame from server → render to terminal
  • PACKET_TYPE_AUDIO_BATCH: Batched audio samples → process for playback
  • PACKET_TYPE_PONG: Ping response → reset keepalive timeout
  • PACKET_TYPE_CLEAR_CONSOLE: Server command → clear terminal
  • PACKET_TYPE_SERVER_STATE: Server state update → log info

Data Reception Loop:

// Receive and decrypt packet header
if (receive_packet(sockfd, crypto_ctx, &header, &payload, &payload_len) < 0) {
server_connection_lost(); // Detect connection loss
break;
}
// Dispatch to handler based on packet type
switch (header.type) {
display_render_frame(payload, is_snapshot);
break;
// ... other packet types ...
}
if (payload) free(payload);
}
void server_connection_lost()
Signal that connection has been lost.
void display_render_frame(const char *frame_data)
Render ASCII frame to display.
int receive_packet(socket_t sockfd, packet_type_t *type, void **data, size_t *len)
Receive a basic packet without encryption.
Definition packet.c:797
@ PACKET_TYPE_ASCII_FRAME
Complete ASCII frame with all metadata.
Definition packet.h:361
Network packet header structure.
Definition packet.h:598
uint16_t type
Packet type (packet_type_t enumeration)
Definition packet.h:602

Connection Loss Detection:

Video Capture

File: src/client/capture.c, src/client/capture.h

Responsibilities:

  • Webcam capture thread management
  • Video frame capture at target FPS
  • Frame compression (optional)
  • Frame transmission to server
  • Snapshot mode coordination

Platform Webcam Support:

Capture Loop:

video_frame_t *frame = webcam_capture();
if (!frame) {
// Webcam error or test pattern mode
continue;
}
// Optional compression
if (opt_compress) {
compressed_data = compress_frame(frame->data, frame->data_size, &compressed_size);
}
// Send to server
send_packet_to_server(PACKET_TYPE_IMAGE_FRAME,
compressed_data ? compressed_data : frame->data,
compressed_data ? compressed_size : frame->data_size,
g_client_id);
// Rate limiting (target FPS)
platform_sleep_usec(frame_interval_usec);
// Snapshot mode: exit after configured delay
if (opt_snapshot && elapsed_time > opt_snapshot_delay) {
break;
}
}
@ PACKET_TYPE_IMAGE_FRAME
Complete RGB image with dimensions.
Definition packet.h:363
int frame
Definition splash.c:99
Video frame structure.

Snapshot Mode:

  • --snapshot: Capture single frame and exit
  • --snapshot-delay <seconds>: Capture for N seconds then exit
  • Useful for testing without continuous streaming

Terminal Display

File: src/client/display.c, src/client/display.h

Responsibilities:

  • Terminal initialization and configuration
  • TTY capability detection
  • ASCII frame rendering
  • Terminal reset and cleanup
  • Logging suppression for first frame

TTY Handling:

// Initialize display subsystem
if (display_init() != 0) {
log_fatal("Failed to initialize display");
return 1;
}
// Check if stdout is a TTY
if (!display_has_tty()) {
log_warn("stdout is not a TTY - frames may not render correctly");
}
// Render frame to terminal
display_render_frame(ascii_frame_data, is_snapshot_frame);
// Cleanup on exit
display_cleanup(); // Restores terminal settings
bool display_has_tty()
Check if display has TTY capability.
int display_init()
Initialize what is necessary to display ascii frames.
void display_cleanup()
Cleanup display subsystem.
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_fatal(...)
Log a FATAL message.
Definition log/log.h:599

Terminal Capabilities:

  • Detects color support (24-bit true color, 256-color, ANSI)
  • Detects Unicode/UTF-8 support
  • Detects half-block character support
  • Sends capabilities to server via PACKET_TYPE_CLIENT_CAPABILITIES

First Frame Optimization:

  • Disables terminal logging before first frame render
  • Prevents log messages from corrupting display
  • Re-enables logging after first frame displayed

Audio Processing

File: src/client/audio.c, src/client/audio.h

Responsibilities:

  • PortAudio initialization and configuration
  • Audio capture thread management
  • Audio sample capture and transmission
  • Received audio sample playback
  • Ring buffer management for audio jitter

Audio Pipeline:

// Initialize audio subsystem
if (audio_client_init() != 0) {
log_error("Failed to initialize audio");
return -1;
}
// Start audio capture thread
if (opt_audio_enabled) {
}
// Audio capture loop (in thread)
float samples[AUDIO_BATCH_SIZE];
if (audio_capture_samples(samples, AUDIO_BATCH_SIZE) > 0) {
send_packet_to_server(PACKET_TYPE_AUDIO, samples,
AUDIO_BATCH_SIZE * sizeof(float), g_client_id);
}
}
// Process received audio from server
void audio_process_received_samples(const float *samples, int num_samples) {
audio_ringbuffer_write(samples, num_samples); // Jitter buffering
}
void audio_process_received_samples(const float *samples, int num_samples)
Process received audio samples from server.
int audio_client_init()
Initialize audio subsystem.
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587

Audio Configuration:

  • Sample rate: 44100 Hz
  • Channels: 1 (mono)
  • Format: 32-bit float
  • Batch size: 256 samples (~5.8ms at 44.1kHz)
  • Ring buffer: 8192 samples (~185ms jitter buffer)

Connection Keepalive

File: src/client/keepalive.c, src/client/keepalive.h

Responsibilities:

  • Ping thread management
  • Periodic ping packet transmission
  • Pong timeout detection
  • Connection liveness monitoring

Keepalive Loop:

#define PING_INTERVAL_SEC 5
#define PONG_TIMEOUT_SEC 30
// Send ping packet
send_packet_to_server(PACKET_TYPE_PING, NULL, 0, g_client_id);
// Wait for ping interval
platform_sleep_usec(PING_INTERVAL_SEC * 1000000);
// Check pong timeout
time_t now = time(NULL);
if (now - last_pong_time > PONG_TIMEOUT_SEC) {
log_warn("Pong timeout - connection may be dead");
break;
}
}
@ PACKET_TYPE_PING
Keepalive ping packet.
Definition packet.h:383

Timeout Behavior:

  • Ping sent every 5 seconds
  • Pong expected within 30 seconds of last pong
  • Timeout triggers reconnection attempt
  • Prevents zombie connections from blocking resources

Client Cryptography

File: src/client/crypto.c, src/client/crypto.h

Responsibilities:

  • Client-side cryptographic handshake
  • SSH key loading and authentication
  • Known hosts verification
  • Server identity validation
  • Encryption context initialization

Handshake Flow:

// Perform client-side handshake
crypto_handshake_result_t result =
perform_crypto_handshake_client(sockfd, &crypto_ctx, opt_server_key,
server_ip, opt_client_key);
switch (result) {
case HANDSHAKE_SUCCESS:
log_info("Secure connection established");
break;
case HANDSHAKE_WARNING_NO_CLIENT_AUTH:
log_warn("Server not verifying client identity");
break;
case HANDSHAKE_ERROR_AUTH_FAILED:
log_error("Client authentication failed");
return CONNECTION_ERROR_AUTH_FAILED; // No retry
case HANDSHAKE_ERROR_HOST_KEY_FAILED:
log_error("Server host key verification failed");
default:
log_error("Handshake failed");
return CONNECTION_ERROR_GENERIC; // Allow retry
}
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
@ CONNECTION_ERROR_GENERIC
Generic error (retry allowed)
@ CONNECTION_ERROR_HOST_KEY_FAILED
Host key verification failed (no retry)
@ CONNECTION_ERROR_AUTH_FAILED
Authentication failure (no retry)
Cryptographic handshake context structure.

Keys Module:

  • Client keys loaded from --client-key path
  • Server keys loaded from --server-key path or fetched via HTTPS
  • Known hosts stored in ~/.ascii-chat/known_hosts
  • SSH/GPG agent integration (Only tested on UNIX for now)
  • Password-protected key support via $ASCII_CHAT_KEY_PASSWORD environment variable or ssh-agent or gpg-agent usage
Note
SSH Key Support: The client supports native encrypted SSH key decryption using bcrypt_pbkdf (libsodium-bcrypt-pbkdf) + BearSSL AES-256-CTR. No external tools required. Alternatively, use ssh-agent or gpg-agent for agent-based password-free authentication (see lib/crypto/ssh and lib/crypto/gpg for implementation).

Command-Line Options

Connection Options:

  • [address][:port] or [session_string] (positional): Server address/port OR ACDS session string
  • --port <port>: Server port (default: 27224) - conflicts with port in positional argument

ACDS Options (for session string connections):

  • --acds-server <address>: ACDS discovery server address (default: auto-detect)
  • --acds-port <port>: ACDS discovery server port (default: 27225)

Media Options:

  • --audio: Enable audio capture and playback
  • --webcam-index <N>: Select webcam device index
  • --webcam-flip: Flip webcam video vertically
  • --test-pattern: Use test pattern instead of webcam

Display Options:

  • --width <N>: Terminal width (auto-detected if not specified)
  • --height <N>: Terminal height (auto-detected if not specified)
  • --color: Enable color output (auto-detected from terminal caps)
  • --stretch: Stretch frames ignoring aspect ratio
  • --half-block: Use half-block rendering for 2x vertical resolution
  • --palette <name>: Select ASCII palette (default: auto-detected)

Cryptography Options:

  • --client-key <path>: Client SSH key for authentication
  • --server-key <path|url>: Server SSH public key for verification
    • Path: ~/.ssh/server_id_ed25519.pub
    • URL: github:username or gitlab:username
  • environment variable ASCII_CHAT_INSECURE_NO_HOST_IDENTITY_CHECK: Set to1 to skip server host key verification (DANGEROUS!)

Testing Options:

  • --snapshot: Capture single frame and exit
  • --snapshot-delay <seconds>: Capture for N seconds then exit
  • --log-file <path>: Log to file instead of stderr

Configuration Options:

  • --config <path>: Load options from TOML config file
  • --config-create <path>: Generate default config file and exit

Error Handling and Exit Codes

The client uses the standard ascii-chat error code system defined in asciichat_error_t. For a complete reference of all exit codes, see Exit Codes.

Common Client Exit Codes:

  • ASCIICHAT_OK (0): Successful execution
  • ERROR_USAGE: Invalid command-line arguments
  • ERROR_CONFIG: Configuration error
  • ERROR_WEBCAM: Webcam initialization failed
  • ERROR_AUDIO: Audio device error
  • ERROR_NETWORK_CONNECT: Cannot connect to server
  • ERROR_CRYPTO_HANDSHAKE: Handshake failed
  • ERROR_AUTH: Authentication failed
  • ERROR_SIGNAL_INTERRUPT: Interrupted by signal (Ctrl+C)
See also
Exit Codes for complete exit code reference
asciichat_error_t for the full enum definition

Error Categories:

Configuration Errors:

  • Invalid command-line arguments (ERROR_USAGE)
  • Missing or invalid config file (ERROR_CONFIG)
  • Webcam initialization failure (ERROR_WEBCAM, ERROR_WEBCAM_IN_USE)
  • Audio initialization failure (ERROR_AUDIO)
  • Display initialization failure (ERROR_TERMINAL)

Connection Errors:

  • Network unreachable (ERROR_NETWORK_CONNECT)
  • Connection refused (ERROR_NETWORK_CONNECT)
  • DNS resolution failure (ERROR_NETWORK_CONNECT)
  • Authentication failure (ERROR_AUTH, ERROR_CRYPTO_HANDSHAKE)
  • Host key verification failure (ERROR_CRYPTO_KEY, ERROR_UNAUTHORIZED)
  • Handshake timeout (ERROR_NETWORK_TIMEOUT)

Runtime Errors (log and attempt reconnect):

  • Socket write error
  • Socket read error
  • Packet decryption failure
  • Frame corruption
  • Keepalive timeout

Synchronization and Thread Safety

Global State Protection

Global Mutex (g_send_mutex):

  • Protects ALL calls to send_packet_to_server()
  • Prevents interleaved packet transmission
  • Ensures packet atomicity on the wire

Atomic Flags:

  • g_should_exit: Global shutdown coordination (checked by all threads)
  • g_connection_active: Connection status (set/checked atomically)
  • g_connection_lost: Connection loss detection (set by any thread)

Thread Exit Coordination:

// Main thread signals shutdown
signal_exit(); // Sets g_should_exit atomically
// All threads check flag in main loop
// Thread work...
}
// Main thread waits for clean exit
capture_stop_thread(); // Signals thread to exit
platform_sleep_usec(10000); // 10ms poll
}
bool capture_thread_exited()
Check if capture thread has exited.
void signal_exit(void)
Definition misc.c:129

Common Race Conditions

Socket FD Race:

  • Problem: Socket closed while thread tries to send
  • Solution: Check server_connection_is_active() before every send
  • Solution: Hold g_send_mutex during entire send operation

Connection Loss Race:

  • Problem: Multiple threads detect connection loss simultaneously
  • Solution: server_connection_lost() uses atomic compare-exchange
  • Solution: Only first detection triggers reconnection

Thread Exit Race:

  • Problem: Main thread starts reconnection while threads still cleaning up
  • Solution: Main thread polls *_thread_exited() before reconnection
  • Solution: Threads set exit flag atomically before returning

Best Practices

DO:

  • Always check should_exit() in thread main loops
  • Always check server_connection_is_active() before sending packets
  • Always hold g_send_mutex when calling send_packet_to_server()
  • Always wait for thread exit before reconnection
  • Always use atomic operations for connection state flags
  • Always cleanup resources in reverse initialization order

DON'T:

  • Don't call send_packet_to_server() without g_send_mutex
  • Don't close socket while threads may be using it
  • Don't start reconnection without waiting for thread exit
  • Don't ignore return values from connection functions
  • Don't use blocking I/O without timeout checking
  • Don't assume terminal is TTY without checking

Debugging and Troubleshooting

Connection Issues:

# Enable debug logging
LOG_LEVEL=0 ./bin/ascii-chat client --log-file=/tmp/client-debug.log
# Test with snapshot mode (no continuous streaming)
./bin/ascii-chat client --snapshot --snapshot-delay 5
# Skip host key verification (testing only!)
export ASCII_CHAT_INSECURE_NO_HOST_IDENTITY_CHECK=1
./bin/ascii-chat client

Webcam Issues:

# List available webcams
./bin/ascii-chat client --webcam-index -1 # Will list and exit
# Use specific webcam
./bin/ascii-chat client --webcam-index 1
# Use test pattern instead of webcam
./bin/ascii-chat client --test-pattern

Audio Issues:

# List audio devices
./bin/ascii-chat client \c --audio --log-level 0 # Check PortAudio logs
# Disable audio if causing issues
./bin/ascii-chat client # Audio disabled by default

Display Issues:

# Check terminal capabilities
LOG_LEVEL=0 ./bin/ascii-chat client --log-file=/tmp/caps.log
grep "Terminal capabilities" /tmp/caps.log
# Force specific dimensions
./bin/ascii-chat client --width 80 --height 24
# Try different palette
./bin/ascii-chat client --palette simple

Client State Machine

The client implements a robust state machine that handles connection lifecycle, reconnection logic, and thread coordination across multiple concurrent operations.

Connection States

State Diagram:

*
*                                 +---> CONNECTED
*                                 |        |
*         START ---> CONNECTING --+        |
*                       |         |        |
*                       |         +-> RECONNECTING
*                       |
*                       +---> FAILED (auth/host key)
*
*      CONNECTED/RECONNECTING ---> (connection loss detected) ---> CLEANUP
*                                                                      |
*      CLEANUP ---> (threads joined) ---> (user exit?) ---> SHUTDOWN
*                                                               |
*                                              (retry?) ---> CONNECTING
* 

State Descriptions:

  • START: Initial state before connection attempt
  • CONNECTING: Attempting to establish socket and handshake
  • CONNECTED: Active connection with all threads running
  • RECONNECTING: Connection lost, exponential backoff active, preparing retry
  • FAILED: Fatal error (auth failed, host key verification failed) - no retry
  • CLEANUP: Stopping threads and releasing resources after connection loss
  • SHUTDOWN: Final cleanup and exit

Session State Management

Session state persists across reconnections to maintain coherent behavior:

typedef struct {
connection_attempt_context_t connection_ctx; // Fallback attempt context (TCP -> STUN -> TURN)
const char *discovered_address; // Server IP discovered via LAN/ACDS
int discovered_port; // Server port
int reconnect_attempt; // Current retry attempt number
bool has_ever_connected; // Track if any connection succeeded
Client connection session state for session_client_like integration.
Context for connection attempts (TCP or WebSocket)

Session Lifecycle:

  • reconnect_attempt = 0: First connection attempt
  • has_ever_connected = false: No successful connection yet (fresh session)
  • On connection success: reconnect_attempt = 0 (reset), has_ever_connected = true (set)
  • On connection loss: reconnect_attempt++ (increment), has_ever_connected remains true
  • On fatal error: State cleared, exit without retry

Fallback Connection Strategy: The connection context maintains state across fallback attempts:

  1. Try Direct TCP: connection_attempt_tcp(address, port)
  2. Try STUN P2P: connection_attempt_stun(address, port) (WebRTC negotiation)
  3. Try TURN Relay: connection_attempt_turn(address, port) (guaranteed success)

Each stage reuses the same context, allowing the client to remember which stages succeeded and avoid repeated failures in subsequent reconnections.

Error Recovery Patterns

The client implements sophisticated error recovery with distinct handling for different error classes:

Fatal Errors (No Recovery)

Error Codes:

  • CONNECTION_ERROR_AUTH_FAILED (-2): Client authentication rejected by server
  • CONNECTION_ERROR_HOST_KEY_FAILED (-3): Server host key verification failed

Recovery Pattern:

// Fatal auth errors - inform user and exit
log_error("Authentication failed - cannot reconnect: %s", error_message);
return result; // Exit immediately
}

Why No Retry: Retrying would be futile - the server has explicitly rejected the client (auth failed) or the client cannot trust the server identity (host key failed). User intervention required (different password, updated host key, etc.).

Transient Errors (With Recovery)

Error Codes:

  • CONNECTION_ERROR_GENERIC (-1): Network error (connection refused, timeout, DNS failure)

Recovery Pattern:

if (result == CONNECTION_ERROR_GENERIC) {
// Transient errors recover with linear backoff
int delay_ms = 100 + (200 * (reconnect_attempt - 1));
if (delay_ms > 5000) delay_ms = 5000;
platform_sleep_usec(delay_ms * 1000);
reconnect_attempt++;
continue; // Try again
}

Backoff Strategy:

  • Initial delay: 100ms
  • Growth: +200ms per attempt
  • Cap: 5 seconds maximum
  • Attempt 1: 100ms
  • Attempt 2: 300ms
  • Attempt 3: 500ms
  • Attempt 4+: 5000ms (capped)

Why Linear Backoff: Prevents thundering herd when server is briefly down. Gives server time to recover from temporary issues while not abandoning the client too quickly.

Runtime Connection Loss (With Recovery)

Detection Triggers:

  1. Socket read error during receive_packet()
  2. Socket write error during send_packet_to_server()
  3. Keepalive timeout (no pong for 30 seconds)
  4. Data reception thread exits unexpectedly

Recovery Pattern:

// Thread A detects loss
if (socket_error_detected) {
server_connection_lost(); // Atomic flag set
}
// Main thread detects loss
if (protocol_connection_lost()) break; // Lost detected
platform_sleep_usec(100000); // 100ms poll
}
// Main thread recovers
protocol_stop_connection(); // Wait for threads
// Loop back to reconnect
reconnect_attempt++;
void display_reset_for_new_connection()
Reset display state for new connection.
bool protocol_connection_lost()
Check if connection has been lost.

Why This Works: By coordinating thread shutdown and resource cleanup in proper order, we ensure no race conditions during reconnection. Each reconnection cycle is a clean restart of all subsystems.

Resource Cleanup Order

Critical: Resources must be cleaned up in reverse initialization order to avoid use-after-free bugs:

Initialization Order (first → last):

  1. Platform initialization
  2. Display initialization
  3. Capture initialization
  4. Audio initialization

Cleanup Order (must be reversed):

  1. Audio cleanup (stop playback)
  2. Capture cleanup (stop webcam)
  3. Display cleanup (restore terminal)
  4. Platform cleanup (close handles)

Why Order Matters: Display cleanup restores terminal state, but if capture is still running and trying to use the terminal, we get corruption. Platform cleanup closes OS handles that other subsystems may still need.

Signal Handler Context: The cleanup handler registered with atexit() follows this order:

signal_exit(); // Stop all threads
splash_intro_done(); // Stop splash animation
protocol_stop_connection(); // Shut down socket, wait for threads
thread_pool_destroy(); // Destroy worker pool
app_client_destroy(); // Destroy client context
server_connection_cleanup(); // Legacy cleanup
capture_cleanup(); // Stop webcam
audio_cleanup(); // Stop audio
display_cleanup(); // Restore terminal
buffer_pool_cleanup_global(); // Free buffer pools
void buffer_pool_cleanup_global(void)
void audio_cleanup()
Cleanup audio subsystem.
void capture_cleanup()
Cleanup capture subsystem.
void server_connection_cleanup()
Cleanup connection management subsystem.
void asciichat_errno_destroy(void)
Cleanup error system resources.
void log_destroy(void)
Destroy the logging system and close log file.
Definition log/log.c:624
void platform_disable_keepawake(void)
Disable system sleep prevention (allow OS to sleep)
void symbol_cache_destroy(void)
Clean up the symbol cache and free all resources.
Definition symbols.c:316
int splash_intro_done(void)
Signal that intro splash should end (first frame ready to render)
Definition splash.c:693
void app_client_destroy(app_client_t **client_ptr)
Destroy client application context and free all resources.
void options_state_destroy(void)
Shutdown RCU options system.
Definition rcu.c:461
void thread_pool_destroy(thread_pool_t *pool)
Destroy a thread pool.

Thread-Safe Error Detection

Connection Loss Detection uses atomic operations to prevent race conditions:

// Atomic compare-exchange ensures only FIRST detector takes action
bool expected = true;
if (atomic_compare_exchange_strong(&g_connection_active, &expected, false)) {
// Only first thread to detect loss sets the flag
atomic_store(&g_connection_lost, true);
log_warn("Connection lost detected by thread");
}
}

Why Atomic CAS: Multiple threads may detect loss simultaneously (data thread, keepalive thread). Using compare-exchange ensures only one thread triggers cleanup, preventing double-cleanup race.

Known Issues and Limitations

SSH Key Decryption:

  • Native encrypted key support using bcrypt_pbkdf + BearSSL AES-256-CTR
  • No external tools (ssh-keygen) required
  • Alternative: Use $SSH_AUTH_SOCK (ssh-agent) for password-free authentication

IPv6 Connection Issues:

  • Some systems prioritize IPv6 but server only listens on IPv4
  • Workaround: Explicitly use IPv4 address
  • Example: ./bin/ascii-chat client 127.0.0.1

Terminal Compatibility:

  • Some terminals don't support UTF-8 or half-block characters
  • Some terminals have incorrect TERM environment variable
  • Workaround: Use --palette simple for ASCII-only rendering

Reconnection Behavior:

  • Reconnection resets frame display (first frame optimization repeats)
  • Brief flicker during reconnection is expected
  • Keepalive timeout (30s) may be too long for some use cases
See also
src/client/main.c
src/client/server.c
src/client/protocol.c
src/client/capture.c
src/client/display.c
src/client/audio.c
src/client/keepalive.c
src/client/crypto.c
lib/crypto/handshake.h
lib/crypto/keys/keys.h
lib/video/webcam/webcam.h
lib/platform/terminal.h
topic_acds For ACDS discovery service and Session Management Module strings