ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
Core Infrastructure

⚙️ Core functionality including error handling, logging, and configuration More...

Modules

 main()
 🚀 The binary's entry point
 

Detailed Description

⚙️ Core functionality including error handling, logging, and configuration

Common definitions, error handling, logging system, and configuration.

Common Definitions

Core error handling, memory management, logging, and type definitions shared throughout the ascii-chat application.

Overview

The common module (lib/common.h, lib/common.c) provides fundamental infrastructure:

  • Comprehensive error handling with context tracking
  • Memory allocation with leak detection in debug builds
  • Rate-limited logging for high-frequency code paths
  • Platform-independent type definitions
  • Shared constants and macros

All application code should use these common definitions to ensure consistency, proper error handling, and debuggability.

Error Handling System

ascii-chat uses a comprehensive error system (lib/asciichat_errno.h) that provides:

  • Specific error codes for different failure modes
  • Contextual error messages with file/line/function information
  • System error integration (errno on POSIX, GetLastError on Windows)
  • Thread-local error state (thread-safe)
  • Stack traces in debug builds

Error Code Enumeration

typedef enum {
// Standard codes (0-2) - Unix conventions
ASCIICHAT_OK = 0, // Success
ERROR_GENERAL = 1, // Unspecified error
ERROR_USAGE = 2, // Invalid command line arguments
// Initialization failures (3-19)
ERROR_MEMORY = 3, // Memory allocation failed (OOM)
ERROR_CONFIG = 4, // Configuration file or settings error
ERROR_CRYPTO_INIT = 5, // Cryptographic initialization failed
ERROR_LOGGING_INIT = 6, // Logging system initialization failed
ERROR_PLATFORM_INIT = 7, // Platform-specific initialization failed
// Hardware/Device errors (20-39)
ERROR_WEBCAM = 20, // Webcam initialization or capture failed
ERROR_WEBCAM_IN_USE = 21, // Webcam is in use by another application
ERROR_WEBCAM_PERMISSION = 22, // Webcam permission denied
ERROR_AUDIO = 23, // Audio device initialization or I/O failed
ERROR_AUDIO_IN_USE = 24, // Audio device is in use
ERROR_TERMINAL = 25, // Terminal initialization failed
// Network errors (40-59)
ERROR_NETWORK = 40, // General network error
ERROR_NETWORK_BIND = 41, // Cannot bind to port (server)
ERROR_NETWORK_CONNECT = 42, // Cannot connect to server (client)
ERROR_NETWORK_TIMEOUT = 43, // Network operation timed out
ERROR_NETWORK_PROTOCOL = 44, // Protocol violation or incompatible version
ERROR_NETWORK_SIZE = 45, // Network packet size error
// Security/Crypto errors (60-79)
ERROR_CRYPTO = 60, // Cryptographic operation failed
ERROR_CRYPTO_KEY = 61, // Key loading, parsing, or generation failed
ERROR_CRYPTO_AUTH = 62, // Authentication failed
ERROR_CRYPTO_HANDSHAKE = 63, // Cryptographic handshake failed
ERROR_CRYPTO_VERIFICATION = 64, // Signature or key verification failed
// Runtime errors (80-99)
ERROR_THREAD = 80, // Thread creation or management failed
ERROR_BUFFER = 81, // Buffer allocation or overflow
ERROR_BUFFER_FULL = 82, // Buffer full
ERROR_BUFFER_OVERFLOW = 83, // Buffer overflow
ERROR_DISPLAY = 84, // Display rendering or output error
ERROR_INVALID_STATE = 85, // Invalid program state
ERROR_INVALID_PARAM = 86, // Invalid parameter
ERROR_INVALID_FRAME = 87, // Invalid frame data
ERROR_RESOURCE_EXHAUSTED = 88, // System resources exhausted
ERROR_FORMAT = 89, // String formatting operation failed
ERROR_STRING = 90, // String manipulation operation failed
// Signal/Crash handlers (100-127)
ERROR_SIGNAL_INTERRUPT = 100, // Interrupted by signal (SIGINT, SIGTERM)
ERROR_SIGNAL_CRASH = 101, // Fatal signal (SIGSEGV, SIGABRT, etc.)
ERROR_ASSERTION_FAILED = 102, // Assertion or invariant violation
// Compression errors (103-104)
ERROR_COMPRESSION = 103, // Compression operation failed
ERROR_DECOMPRESSION = 104, // Decompression operation failed
} asciichat_error_t;

Error Context

Each error includes rich contextual information:

typedef struct {
asciichat_error_t code; // Error code (asciichat_error_t enum value)
const char *file; // Source file where error occurred (NULL in release)
int line; // Line number where error occurred (0 in release)
const char *function; // Function name where error occurred (NULL in release)
char *context_message; // Optional custom message (dynamically allocated)
uint64_t timestamp; // Timestamp when error occurred (microseconds)
int system_errno; // System errno value (if applicable, 0 otherwise)
int wsa_error; // Windows socket error code (if applicable)
void *backtrace[32]; // Stack trace addresses (debug builds only)
char **backtrace_symbols; // Stack trace symbol strings (debug builds only)
int stack_depth; // Number of stack frames captured
bool has_system_error; // True if system_errno is valid
bool has_wsa_error; // True if wsa_error is valid
} asciichat_error_context_t;

Error Handling Macros

SET_ERRNO: Set error with custom message

asciichat_error_t open_file(const char *path) {
if (!path) {
return SET_ERRNO(ERROR_INVALID_PARAM, "File path is NULL");
}
int fd = open(path, O_RDONLY);
if (fd < 0) {
return SET_ERRNO_SYS(ERROR_NOT_FOUND, "Failed to open file: %s", path);
}
return ASCIICHAT_OK;
}

SET_ERRNO_SYS: Set error with system errno

asciichat_error_t create_socket(void) {
socket_t sockfd = socket(AF_INET6, SOCK_STREAM, 0);
if (sockfd == INVALID_SOCKET_VALUE) {
return SET_ERRNO_SYS(ERROR_NETWORK_SOCKET,
"socket() failed (errno=%d)", errno);
}
return ASCIICHAT_OK;
}
int socket_t

HAS_ERRNO: Check if error occurred

asciichat_error_context_t err_ctx;
asciichat_error_t result = risky_operation();
if (HAS_ERRNO(&err_ctx)) {
log_error("Operation failed: %s (code=%d, file=%s:%d)",
err_ctx.context_message, err_ctx.error_code,
err_ctx.file, err_ctx.line);
return result;
}

PRINT_ERRNO_CONTEXT: Debug print error details

if (HAS_ERRNO(&err_ctx)) {
PRINT_ERRNO_CONTEXT(&err_ctx); // Only in debug builds
// Output:
// Error: ERROR_NETWORK_CONNECT (code=13)
// Message: Failed to connect to server (Connection refused)
// Location: src/client.c:245 in connect_to_server()
// System errno: 111 (Connection refused)
// Timestamp: 1234567890123456 µs
}

CLEAR_ERRNO: Clear error state

CLEAR_ERRNO(); // Resets thread-local error state

GET_ERRNO: Get current error code

asciichat_error_t current_error = GET_ERRNO();
if (current_error != ASCIICHAT_OK) {
// Handle error
}

Memory Management

Custom memory allocation macros with automatic leak detection in debug builds:

Memory Allocation Macros

SAFE_MALLOC: Allocate memory with leak tracking

// Syntax: SAFE_MALLOC(size, cast_type)
uint8_t *buffer = SAFE_MALLOC(1024, uint8_t*);
if (!buffer) {
return SET_ERRNO(ERROR_MEMORY, "Failed to allocate 1024 bytes");
}
// In debug builds, records:
// - Allocation address
// - Size (1024 bytes)
// - File (src/client.c)
// - Line (123)
// - Thread ID

SAFE_CALLOC: Allocate zeroed memory

// Allocate array of 100 integers, initialized to 0
int *array = SAFE_CALLOC(100, sizeof(int), int*);
if (!array) {
return ERROR_MEMORY;
}

SAFE_REALLOC: Reallocate memory

// Grow buffer from 1024 to 2048 bytes
uint8_t *new_buffer = SAFE_REALLOC(buffer, 2048, uint8_t*);
if (!new_buffer) {
// Original buffer still valid
SAFE_FREE(buffer);
return ERROR_MEMORY;
}
buffer = new_buffer;

SAFE_FREE: Free memory and set to NULL

SAFE_FREE(buffer); // Frees and sets buffer = NULL
// In debug builds, removes from leak tracker

Debug Memory Tracking

When CMAKE_BUILD_TYPE=Debug and DEBUG_MEMORY is defined:

Leak Detection:

  • All SAFE_MALLOC/CALLOC/REALLOC calls are recorded
  • Each allocation stores: address, size, file, line, timestamp
  • On program exit, reports any allocations not freed
  • Example output:
    Memory leak detected:
    Address: 0x7f1234567890
    Size: 1024 bytes
    Allocated at: src/client.c:123 in process_frame()
    Thread: 0x7f123456
    Total leaked: 1024 bytes in 1 allocation

Allocation Logging:

#define DEBUG_MEMORY 1
// Every allocation is logged:
// [MEMORY] malloc(1024) = 0x7f1234567890 at src/client.c:123
// [MEMORY] free(0x7f1234567890) at src/client.c:456

Double-Free Detection:

  • Freeing already-freed memory triggers assertion
  • Prevents use-after-free bugs

Memory Reports:

void asciichat_common_print_memory_stats(void);
// Prints:
// Current allocations: 5 (total 4096 bytes)
// Peak allocations: 10 (total 8192 bytes)
// Total malloc calls: 100
// Total free calls: 95

Logging System

Multi-level logging with rate limiting for high-frequency code:

Log Levels

typedef enum {
LOG_DEBUG = 0, // Verbose debug information
LOG_INFO = 1, // Informational messages
LOG_WARN = 2, // Warnings (non-fatal issues)
LOG_ERROR = 3, // Errors (operation failed)
LOG_FATAL = 4, // Fatal errors (program will exit)
} log_level_t;

Control via environment:

export LOG_LEVEL=0 # DEBUG (all messages)
export LOG_LEVEL=1 # INFO and above
export LOG_LEVEL=2 # WARN and above
export LOG_LEVEL=3 # ERROR and above
export LOG_LEVEL=4 # FATAL only

Standard Logging Macros

log_debug("Frame captured: %dx%d", width, height);
log_info("Client connected from %s:%d", ip, port);
log_warn("Audio buffer underrun detected");
log_error("Failed to send packet: %s", strerror(errno));
log_fatal("Critical error, terminating"); // Exits program

Output format:

[2025-01-15 14:32:45.123] [DEBUG] Frame captured: 1920x1080
[2025-01-15 14:32:45.456] [INFO] Client connected from 192.168.1.100:12345
[2025-01-15 14:32:45.789] [WARN] Audio buffer underrun detected
[2025-01-15 14:32:46.012] [ERROR] Failed to send packet: Broken pipe
[2025-01-15 14:32:46.345] [FATAL] Critical error, terminating

Rate-Limited Logging

For high-frequency code paths (e.g., video/audio processing at 60+ FPS):

// Log at most once per second (1000000 µs)
log_debug_every(1000000, "Processing frame %d", frame_count);
// Log at most once per 5 seconds
log_info_every(5000000, "Client status: %d connected", num_clients);
// Log at most once per 10 seconds
log_warn_every(10000000, "High CPU usage: %.1f%%", cpu_usage);

Implementation:

  • Uses thread-local timestamp tracking
  • First call always logs
  • Subsequent calls within interval are suppressed
  • Different log sites have independent rate limiting

Example (video processing loop at 60 FPS):

while (running) {
capture_frame();
// Without rate limiting: 60 log messages per second
// log_debug("Frame captured");
// With rate limiting: 1 log message per second
log_debug_every(1000000, "Frame captured (count=%d)", frame_count);
frame_count++;
}

Logging Configuration

// Initialize logging system
asciichat_error_t log_init(const char *log_file, log_level_t level);
// Change log level at runtime
void log_set_level(log_level_t level);
// Enable/disable color output
void log_set_color(bool enable);
// Cleanup logging
void log_destroy(void);
void log_destroy(void)
void log_set_level(log_level_t level)
void log_init(const char *filename, log_level_t level, bool force_stderr, bool use_mmap)

Usage:

// Log to file and stderr
log_init("/tmp/ascii-chat.log", LOG_INFO);
// Later: increase verbosity
log_set_level(LOG_DEBUG);
// Cleanup

Common Type Definitions

Fixed-width integers (platform-independent):

#include <stdint.h>
int8_t, uint8_t // 8-bit signed/unsigned
int16_t, uint16_t // 16-bit signed/unsigned
int32_t, uint32_t // 32-bit signed/unsigned
int64_t, uint64_t // 64-bit signed/unsigned
size_t // Unsigned size type (32 or 64-bit)
ssize_t // Signed size type

Boolean (C99 style):

#include <stdbool.h>
bool, true, false
#define bool
Definition stdbool.h:22

NULL pointer:

#define NULL ((void*)0)

Common Constants

// Buffer sizes
#define MAX_PACKET_SIZE 65536 // Maximum network packet payload
#define MAX_PATH_LEN 4096 // Maximum path length
#define MAX_CLIENTS 9 // Maximum simultaneous clients
// Network
#define DEFAULT_PORT 27224 // Default server port
#define PACKET_MAGIC 0xDEADBEEF // Packet header magic number
// Video
#define DEFAULT_WIDTH 80 // Default terminal width
#define DEFAULT_HEIGHT 24 // Default terminal height
#define DEFAULT_FPS 30 // Default framerate
// Audio
#define SAMPLE_RATE 48000 // Audio sample rate (Hz)
#define AUDIO_CHANNELS 1 // Mono audio
#define FRAMES_PER_BUFFER 512 // PortAudio buffer size

Usage Examples

Comprehensive Error Handling

asciichat_error_t initialize_system(const char *config_file) {
asciichat_error_t err;
// Load configuration
options_t opts;
err = config_load(&opts, config_file);
if (err != ASCIICHAT_OK) {
asciichat_error_context_t ctx;
if (HAS_ERRNO(&ctx)) {
log_error("Config load failed: %s", ctx.context_message);
PRINT_ERRNO_CONTEXT(&ctx);
}
return err;
}
// Initialize crypto
crypto_context_t crypto_ctx;
err = crypto_init(&crypto_ctx);
if (err != ASCIICHAT_OK) {
options_destroy(&opts);
return err;
}
// Initialize audio
audio_context_t audio_ctx;
err = audio_init(&audio_ctx);
if (err != ASCIICHAT_OK) {
crypto_destroy(&crypto_ctx);
options_destroy(&opts);
return err;
}
return ASCIICHAT_OK;
}
asciichat_error_t audio_init(audio_context_t *ctx)
crypto_result_t crypto_init(crypto_context_t *ctx)
void crypto_destroy(crypto_context_t *ctx)

Memory Management with Leak Detection

void process_data(void) {
// Allocate buffers
uint8_t *buffer1 = SAFE_MALLOC(1024, uint8_t*);
uint8_t *buffer2 = SAFE_CALLOC(512, sizeof(uint8_t), uint8_t*);
if (!buffer1 || !buffer2) {
log_error("Memory allocation failed");
SAFE_FREE(buffer1);
SAFE_FREE(buffer2);
return;
}
// Process data...
// Grow buffer if needed
if (need_more_space) {
uint8_t *new_buffer = SAFE_REALLOC(buffer1, 2048, uint8_t*);
if (!new_buffer) {
log_error("Reallocation failed");
SAFE_FREE(buffer1);
SAFE_FREE(buffer2);
return;
}
buffer1 = new_buffer;
}
// Cleanup (SAFE_FREE sets pointers to NULL)
SAFE_FREE(buffer1);
SAFE_FREE(buffer2);
}
// At program exit (debug builds):
// If any SAFE_MALLOC'd memory wasn't freed, it's reported:
// Memory leak detected:
// Address: 0x7f1234567890
// Size: 1024 bytes
// Allocated at: src/client.c:123

Rate-Limited Logging in Video Loop

void video_processing_loop(void) {
uint64_t frame_count = 0;
uint64_t dropped_frames = 0;
while (running) {
// Capture frame (60 FPS)
uint8_t *frame_data;
asciichat_error_t err = webcam_capture_frame(webcam, &frame_data, &frame_len);
if (err != ASCIICHAT_OK) {
dropped_frames++;
// Log at most once per second to avoid spam
log_warn_every(1000000, "Frame capture failed (dropped %lu frames)",
dropped_frames);
continue;
}
// Process frame...
frame_count++;
// Log progress every 5 seconds
log_info_every(5000000, "Processed %lu frames (%.1f FPS)",
frame_count, calculate_fps());
}
}
See also
common.h
asciichat_errno.h
logging.h

Configuration README

Command-line argument parsing and TOML-based configuration file management for flexible server and client setup.

Overview

ascii-chat supports two configuration methods:

  1. Command-line arguments: Quick configuration for ad-hoc usage
  2. TOML configuration files: Persistent, structured configuration

Command-line arguments override configuration file settings, allowing:

  • Default configuration in ~/.config/ascii-chat/config.toml
  • Per-session overrides via command-line flags
  • Easy scripting and automation

TOML Configuration Files

ascii-chat uses TOML (Tom's Obvious, Minimal Language) for configuration files, parsed by the tomlc17 library.

File Format

Complete example configuration:

# ascii-chat Configuration File
# Location: ~/.config/ascii-chat/config.toml
[server]
# Bind address ("::" for all IPv6/IPv4, "0.0.0.0" for IPv4 only)
address = "::"
# Server port (default: 27224)
port = 27224
# Maximum simultaneous clients (1-9)
max_clients = 9
# Server identity key (SSH Ed25519 format)
key_file = "~/.ssh/ascii-chat-server"
# Authorized client public keys (SSH format or github:username)
# Leave empty to allow all clients (less secure)
client_keys = [
"github:alice",
"github:bob",
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... charlie@example.com"
]
[client]
# Server address to connect to
address = "localhost"
# Server port
port = 27224
# Expected server public key (for TOFU verification)
# Leave empty to accept any server (prompts for confirmation)
server_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI..."
# Client identity key
key_file = "~/.ssh/id_ed25519"
# Connection timeout (seconds)
timeout = 10
[video]
# Terminal width (auto-detected if not specified)
width = 160
# Terminal height (auto-detected if not specified)
height = 45
# Enable color output (true/false)
color = true
# Use half-block characters for 2x vertical resolution
half_blocks = true
# ASCII palette (characters from dark to bright)
palette = " .:-=+*#%@"
# Webcam device index (0 = first camera)
webcam_device = 0
# Capture resolution (before scaling to terminal size)
capture_width = 1920
capture_height = 1080
# Target framerate
fps = 30
[audio]
# Enable audio capture/playback
enabled = true
# Sample rate (Hz)
sample_rate = 48000
# Audio channels (1=mono, 2=stereo)
channels = 1
# PortAudio buffer size (frames)
buffer_size = 512
# Audio input device (-1 = default)
input_device = -1
# Audio output device (-1 = default)
output_device = -1
[logging]
# Log file path (empty = stderr only)
file = "/tmp/ascii-chat.log"
# Log level (debug, info, warn, error, fatal)
level = "info"
# Enable color in log output
color = true

Configuration Sections

Section Names: The section names in the TOML file ([server], [client], [video], [audio], [logging]) correspond directly to the option categories displayed in ascii-chat --help and the man page. You can find the complete list of available sections by running ascii-chat --help and looking at the category labels for each option.

Key Names: Configuration keys are the long-form command-line option names with dashes converted to underscores. For example:

  • --color-mode flag becomes color_mode key in config
  • --max-clients flag becomes max_clients key in config
  • --server-key flag becomes server_key key in config

To find the correct key name for any configuration option, take the long-form flag name (displayed in --help output), remove the leading dashes, and replace all dashes with underscores.

Conflicting Options: Some configuration options conflict with each other and will cause the configuration file to fail loading if set simultaneously. For example:

  • --file and --url cannot both be set (URL takes priority)
  • --loop cannot be used with --url (network streams cannot be looped)
  • --seek requires either --file or --url

If your configuration file fails to load with validation errors, check the error message for conflicting options and remove one of them.

Loading Configuration

Load Config File:

#include "config.h"
#include "options.h"
options_t opts;
asciichat_error_t err = config_load(&opts, "~/.config/ascii-chat/config.toml");
if (err != ASCIICHAT_OK) {
log_error("Failed to load config file");
return err;
}
// Configuration is now available in opts structure
log_info("Server address: %s:%d", opts.address, opts.port);
log_info("Max clients: %d", opts.max_clients);
log_info("Video: %dx%d @ %d FPS", opts.width, opts.height, opts.fps);
// Cleanup
options_destroy(&opts);
WASM option accessor declarations.

Apply Defaults:

// Initialize with default values
options_t opts = {0};
apply_default_options(&opts);

Command-Line Options

Server Options

# Start server with defaults
./ascii-chat server
# Bind to specific address and port
./ascii-chat server :: --port 27224
# Bind to IPv4 only
./ascii-chat server 0.0.0.0 --port 27224
# Use server identity key
./ascii-chat server --key ~/.ssh/ascii-chat-server
# Authorize specific clients
./ascii-chat server --client-keys github:alice,github:bob
# Limit maximum clients
./ascii-chat server --max-clients 4
# Enable color and set log file
./ascii-chat server --color --log-file /tmp/server.log
# Use configuration file
./ascii-chat --config ~/.config/ascii-chat/server.toml server

Server-specific flags:

  • [address1] [address2] (positional): Bind addresses, 0-2 IPv4/IPv6 addresses (default: 127.0.0.1 and ::1)
  • --port, -p PORT: Listen port (default: 27224)
  • --key KEY_FILE: Server identity key (SSH Ed25519)
  • --client-keys KEYS: Comma-separated authorized client keys
  • --max-clients N: Maximum simultaneous clients (1-9, default: 9)

Client Options

# Connect to localhost (default)
./ascii-chat client
# Connect to specific server
./ascii-chat client example.com:27224
# Specify terminal dimensions
./ascii-chat client --width 160 --height 45
# Enable color and half-blocks
./ascii-chat client --color --half-blocks
# Use custom ASCII palette
./ascii-chat client --palette " .:-=+*#%@"
# Enable audio
./ascii-chat client --audio
# Verify server identity
./ascii-chat client --server-key ~/.ssh/server_key.pub
# Use client identity key
./ascii-chat client --key ~/.ssh/id_ed25519
# Snapshot mode (capture and exit)
./ascii-chat client --snapshot --snapshot-delay 10
# Use configuration file
./ascii-chat --config ~/.config/ascii-chat/client.toml client example.com

Client-specific flags:

  • [address][:port] (positional): Server address with optional port (default: localhost:27224)
  • --port, -p PORT: Server port (default: 27224) - conflicts with port in positional argument
  • --width, -w WIDTH: Terminal width (default: auto-detect)
  • --height, -h HEIGHT: Terminal height (default: auto-detect)
  • --color, -c: Enable color output
  • --half-blocks: Use half-block characters (2x vertical resolution)
  • --palette, -P PALETTE: ASCII palette type (standard, blocks, digital, minimal, cool, custom)
  • --palette-chars, -C CHARS: Custom palette characters string
  • --audio, -A: Enable audio capture/playback
  • --server-key KEY: Expected server public key (TOFU)
  • --key KEY_FILE: Client identity key
  • --snapshot: Snapshot mode (capture once and exit)
  • --snapshot-delay SECONDS: Snapshot duration before exit

Common Options

Available for both server and client:

  • --config FILE: Configuration file path
  • –log-file FILE: Log file path
  • –help, -?: Show help message
  • –version, -v: Show version information

Default Config Locations

All ascii-chat data files (config.toml, known_hosts) are stored in a single directory:

Linux/macOS:

  • $XDG_CONFIG_HOME/ascii-chat/ (if XDG_CONFIG_HOME is set)
  • ~/.ascii-chat/ (default when XDG_CONFIG_HOME is not set)

Windows:

  • APPDATA%\ascii-chat\ (if APPDATA is set)
  • ~\.ascii-chat\ (default when APPDATA is not set)

Creating a Default Config:

# Create config at default location
./ascii-chat --config-create
# Create config at custom location
./ascii-chat --config-create ~/.my-config.toml
# Overwrite existing config (scripted - supports piped input)
echo "y" | ./ascii-chat --config-create
yes | ./ascii-chat --config-create /tmp/server.toml

Using a Config File:

./ascii-chat --config ~/.ascii-chat/config.toml server
./ascii-chat --config ~/.ascii-chat/config.toml client example.com

Overriding Config with CLI:

# Config file sets port=27224, but override it with --port
./ascii-chat --config ~/.ascii-chat/config.toml client --port 8080

Configuration Precedence

Settings are applied in this order (later overrides earlier):

  1. Built-in defaults - Hardcoded default values
  2. Config file - Values from TOML configuration (if specified with –config)
  3. Environment variables - LOG_LEVEL, etc.
  4. Command-line arguments - Highest priority

Example:

# Config file has port=27224
# Environment has LOG_LEVEL=1
# Command line has --port 8080
# Result: port=8080 (CLI wins), log_level=1 (from env)

Configuration Validation

The configuration system automatically validates all settings:

Automatic Validation:

  • Port range validation (1-65535)
  • Webcam index validation
  • File path validation (key files, config files)
  • Log level validation (0-4)
  • IP address format validation
  • Dimensions validation (width: 20-500, height: 10-200)
  • Client limits (max_clients: 1-9, grid layout constraint)
  • Tilde expansion for home directory (~/)
  • SSH keys must be Ed25519 format
  • Palette strings (must have at least 2 characters, ordered dark → bright)
  • UTF-8 validation for multi-byte characters

Error Reporting:

asciichat_error_t err = config_load(&opts, "~/.ascii-chat/config.toml");
if (err != ASCIICHAT_OK) {
asciichat_error_context_t ctx;
if (HAS_ERRNO(&ctx)) {
log_error("Invalid configuration: %s", ctx.context_message);
}
return err;
}

Example validation code:

asciichat_error_t validate_config(const options_t *opts) {
if (opts->port < 1 || opts->port > 65535) {
return SET_ERRNO(ERROR_CONFIG_INVALID,
"Port must be 1-65535 (got %d)", opts->port);
}
if (opts->max_clients < 1 || opts->max_clients > 9) {
return SET_ERRNO(ERROR_CONFIG_INVALID,
"max_clients must be 1-9 (got %d)", opts->max_clients);
}
if (opts->palette && strlen(opts->palette) < 2) {
return SET_ERRNO(ERROR_CONFIG_INVALID,
"Palette must have at least 2 characters");
}
return ASCIICHAT_OK;
}

Usage Examples

Parsing Command-Line and Config File

int main(int argc, char **argv) {
options_t opts;
asciichat_error_t err;
// Parse command-line arguments
err = options_parse(&opts, argc, argv);
if (err != ASCIICHAT_OK) {
fprintf(stderr, "Usage: %s [server|client] [OPTIONS]\n", argv[0]);
return 1;
}
// Load configuration file if specified
if (opts.config_file) {
err = config_load(&opts, opts.config_file);
if (err != ASCIICHAT_OK) {
log_error("Failed to load config: %s", opts.config_file);
options_destroy(&opts);
return 1;
}
}
// Validate configuration
err = validate_config(&opts);
if (err != ASCIICHAT_OK) {
asciichat_error_context_t ctx;
if (HAS_ERRNO(&ctx)) {
log_error("Invalid configuration: %s", ctx.context_message);
}
options_destroy(&opts);
return 1;
}
// Use configuration...
if (opts.mode == MODE_SERVER) {
run_server(&opts);
} else {
run_client(&opts);
}
// Cleanup
options_destroy(&opts);
return 0;
}
int main(int argc, char *argv[])
Definition main.c:281

Setting Defaults

void options_set_defaults(options_t *opts) {
memset(opts, 0, sizeof(*opts));
// Network defaults
opts->port = DEFAULT_PORT; // 27224
opts->address = strdup("::"); // Dual-stack IPv6/IPv4
// Video defaults
opts->width = DEFAULT_WIDTH; // 80 (or auto-detect)
opts->height = DEFAULT_HEIGHT; // 24 (or auto-detect)
opts->fps = DEFAULT_FPS; // 30
opts->color = true;
opts->half_blocks = false;
opts->palette = strdup(" .:-=+*#%@");
// Audio defaults
opts->audio = false; // Disabled by default for client
opts->sample_rate = SAMPLE_RATE; // 48000
opts->audio_channels = AUDIO_CHANNELS; // 1 (mono)
// Logging defaults
opts->log_level = LOG_INFO;
opts->log_color = true;
}
See also
Options for command-line argument parsing
options.h
config.h

Logging README

Overview

Welcome to the logging system! If you've ever tried to debug a complex program without good logging, you know the pain. The logging system is your debugging best friend—it helps you understand what's happening in your application, track down bugs, and monitor behavior in production.

Think of logging as your application's diary. It writes down everything that happens: when clients connect, when errors occur, when packets are processed. You can configure how chatty it should be (from super verbose DEBUG mode to quiet ERROR-only mode), and you can send the output to the console, to a file, or both!

Implementation: lib/logging.h

What makes this logging system helpful?

  • Six log levels: DEV, DEBUG, INFO, WARN, ERROR, FATAL (choose your verbosity)
  • Rate limiting: Prevents log spam in tight loops (your disk will thank you)
  • Flexible output: Console, file, or both simultaneously
  • Thread-safe: Multiple threads can log without stepping on each other
  • Context tracking: Automatic timestamps and source location (file:line)
  • Pretty colors: Colored console output so errors jump out at you
  • Runtime configuration: Change log level on the fly via environment variable

Log Levels

The logging system has six levels, from most verbose to least:

DEV (0) - The super chatty one:

  • Use this for development-only messages that are too verbose for normal debugging
  • Useful for tracing code paths during active development
  • Usually disabled even in debug builds unless you need extreme detail

DEBUG (1) - The chatty one:

  • Use this when you need detailed diagnostic information
  • Only available in debug builds (stripped out in release for performance)
  • Perfect for understanding exactly what's happening step by step

INFO (2) - The reporter:

  • General informational messages about normal operations
  • "Client connected", "Frame processed", that sort of thing
  • What you'd want to see during normal operation

WARN (3) - The concerned citizen:

  • Something's not quite right, but we can keep going
  • "Buffer is getting full", "Retrying connection"
  • Pay attention, but don't panic

ERROR (4) - Houston, we have a problem:

  • Operation failures that are recoverable
  • "Failed to send packet", "Client disconnected unexpectedly"
  • Things went wrong, but we're handling it

FATAL (5) - The showstopper:

  • Unrecoverable errors that mean we can't continue
  • "Cannot initialize crypto", "Out of memory"
  • Time to gracefully shut down

Basic Usage

Basic Logging

Log Messages:

log_debug("Processing frame %d", frame_number);
log_info("Client connected: %s:%d", ip, port);
log_warn("Buffer nearly full: %zu/%zu", used, capacity);
log_error("Failed to send packet: %s", error_msg);
log_fatal("Cannot initialize crypto: %s", reason);

Format Specifiers:

log_info("Integer: %d", 42);
log_info("String: %s", "hello");
log_info("Pointer: %p", ptr);
log_info("Size: %zu bytes", size);
log_info("Hex: 0x%08x", value);

Rate-Limited Logging

Rate Limiting (prevents log spam in tight loops):

// Log at most once per second (1,000,000 microseconds)
for (int i = 0; i < 1000000; i++) {
log_debug_every(1000000, "Processing item %d", i);
}
// Log at most once per 5 seconds
while (video_running) {
log_info_every(5000000, "Video frames processed: %d", frame_count);
process_frame();
}

Available Rate-Limited Macros:

  • log_debug_every(usec, ...)
  • log_info_every(usec, ...)
  • log_warn_every(usec, ...)
  • log_error_every(usec, ...)

Configuration

Setting Log Level

Runtime Configuration:

// Set log level to DEBUG
set_log_level(LOG_LEVEL_DEBUG);
// Set log level to INFO (hide debug messages)
set_log_level(LOG_LEVEL_INFO);
// Set log level to ERROR (only errors and fatal)
set_log_level(LOG_LEVEL_ERROR);

Environment Variable:

# Set log level via environment
export LOG_LEVEL=0 # DEV
export LOG_LEVEL=1 # DEBUG
export LOG_LEVEL=2 # INFO
export LOG_LEVEL=3 # WARN
export LOG_LEVEL=4 # ERROR
export LOG_LEVEL=5 # FATAL
# Run with debug logging
LOG_LEVEL=1 ./bin/ascii-chat server

File Output

Configure File Logging:

// Enable logging to file
set_log_file("/tmp/ascii-chat-server.log");
// Logs now go to both console and file
log_info("This appears in both console and file");
// Close log file
close_log_file();

Command-line Option:

# Log to file via command line
./bin/ascii-chat server --log-file=/tmp/server.log
./bin/ascii-chat client --log-file=/tmp/client.log

Output Format

Log messages have a consistent format that makes them easy to read and understand. Every message includes a timestamp, level, source location, and the actual message text. Let's look at the format:

What does a log message look like?

Log Message Format:

[TIMESTAMP] LEVEL [file:line] message

The format is simple and consistent—timestamp first (so you can see when things happened), level next (so you know how important it is), source location (so you know where in the code it came from), and the message itself.

Example Output:

[2025-01-15 14:23:45] DEBUG [server.c:234] Starting video capture thread
[2025-01-15 14:23:45] INFO [network.c:456] Client connected: 192.168.1.100:54321
[2025-01-15 14:23:46] WARN [audio.c:123] Audio buffer 80% full
[2025-01-15 14:23:47] ERROR [crypto.c:789] Handshake failed: invalid signature

Notice how you can see exactly when things happened, what level they are, where in the code they came from, and what the message is. This makes debugging much easier!

What about colors?

Console Colors (ANSI terminals):

  • DEV: Dim Gray (even more subtle, for development tracing)
  • DEBUG: Gray (subtle, so it doesn't distract from important messages)
  • INFO: White (default color, for normal messages)
  • WARN: Yellow (gets your attention, but not too alarming)
  • ERROR: Red (you should pay attention to this!)
  • FATAL: Bold Red (this is serious!)

Colors make it easy to scan logs—errors jump out in red, warnings in yellow. If you're in a terminal that supports colors, this makes debugging much easier.

Thread Safety

Mutex Protection:

  • All logging operations are protected by a global mutex
  • Safe for concurrent logging from multiple threads
  • No output interleaving

Thread Identification:

  • Thread ID automatically captured (debug builds)
  • Helps trace multi-threaded execution

Example Multi-threaded Logging:

void* worker_thread(void *arg) {
log_info("Worker thread started"); // Thread-safe
for (int i = 0; i < 1000; i++) {
// Rate-limited, thread-safe
log_debug_every(1000000, "Processing item %d", i);
}
log_info("Worker thread finished");
return NULL;
}

Performance Considerations

Logging performance is important—you don't want logging to slow down your application. We've designed the system to be efficient, but there are some things to keep in mind:

How do we keep logging fast?

Rate Limiting:

  • Use log_*_every() in high-frequency code paths: If you're logging in a tight loop, use rate limiting so you don't flood the logs
  • Prevents log spam and performance degradation: Without rate limiting, logging can slow down your application significantly
  • Minimal overhead when rate limit is active: Once the rate limit is active, logging is essentially free (we just check if enough time has passed, then skip logging)

Rate limiting is your friend in tight loops—you can log every iteration without actually logging every iteration!

How about file I/O?

File I/O:

  • File logging adds I/O overhead: Writing to a file is slower than writing to console, but it's still pretty fast for most use cases
  • Consider async file writing for high-throughput: If you're logging a lot (thousands of messages per second), you might want async file writing
  • Use buffered output for better performance: We use buffered I/O, so multiple log messages can be written in one operation

File logging is pretty fast—we use buffered I/O, so most of the time you won't notice the overhead. If you're logging hundreds of messages per second, you might want to consider rate limiting or async logging.

What about string formatting?

String Formatting:

  • Avoid expensive formatting in hot paths: String formatting (like sprintf()) can be slow if you're doing it thousands of times per second
  • Use rate limiting for frequent messages: If you're logging frequently, rate limiting reduces the number of format operations
  • Consider conditional compilation for debug logs: Debug logs can be compiled out entirely in release builds, so they have zero overhead

String formatting is usually pretty fast, but if you're doing it millions of times per second, it can add up. Use rate limiting for frequent messages, and remember that debug logs are compiled out in release builds!

Best Practices

DO:

  • Use appropriate log levels
  • Use rate limiting in tight loops
  • Include relevant context (IDs, values)
  • Use structured messages
  • Log errors with error codes

DON'T:

  • Don't use printf() directly (use log_*() instead)
  • Don't log in tight loops without rate limiting
  • Don't log sensitive data (passwords, keys)
  • Don't use DEBUG level for production
  • Don't log every iteration of a loop

Examples:

// ✅ GOOD - Rate limited
for (int i = 0; i < 1000000; i++) {
log_debug_every(1000000, "Processed %d items", i);
}
// ❌ BAD - Logs 1 million times!
for (int i = 0; i < 1000000; i++) {
log_debug("Processing item %d", i);
}
// ✅ GOOD - Contextual error message
log_error("Failed to send packet to client %u: %s", client_id, error_msg);
// ❌ BAD - Generic message
log_error("Send failed");

Integration with Error System

Automatic Error Logging:

  • SET_ERRNO() macros automatically log errors
  • Error context included in log messages
  • File/line/function captured automatically

Example:

if (socket_fd < 0) {
// Automatically logs: "ERROR: Failed to create socket"
return SET_ERRNO(ERROR_NETWORK_SOCKET, "Failed to create socket");
}
See also
logging.h
asciichat_errno.h
common.h

Options README

Overview

The Options system provides command-line argument parsing support for ascii-chat. It handles server and client options, validates settings, and manages application configuration.

Implementation: lib/options.h, lib/config.h

Key Features:

  • Command-line argument parsing with getopt
  • Server and client mode options
  • Automatic help generation
  • Configuration validation
  • Default value management

Operation Modes

Server Mode:

./ascii-chat server [options]

Client Mode:

./ascii-chat client [options]

Common Options

Network Options:

  • [address][:port] (positional for client) - Server address with optional port (default: localhost:27224)
  • [address1] [address2] (positional for server) - Bind addresses, 0-2 IPv4/IPv6 addresses
  • --port PORT - Server port (default: 27224)

Display Options:

  • --width WIDTH - Terminal width (default: auto-detect)
  • --height HEIGHT - Terminal height (default: auto-detect)
  • --color - Enable color output
  • --stretch - Stretch video to fill terminal
  • --half-block - Use half-block rendering (2x vertical resolution)

Video Options:

  • --webcam INDEX - Webcam device index (default: 0)
  • --webcam-flip - Flip webcam horizontally
  • --test-pattern - Use test pattern instead of webcam
  • --snapshot - Capture single frame and exit
  • --snapshot-delay SEC - Delay before snapshot (default: 1.0)

Audio Options:

  • --audio - Enable audio (client only, server always has audio)
  • --no-audio - Disable audio

Crypto Options:

  • --key PATH - SSH private key for authentication
  • --server-key KEY - Server public key or github:username
  • --password PASS - Shared password for encryption
  • --insecure - Skip host identity verification (NOT RECOMMENDED)
  • --client-keys KEYS - Whitelist of allowed client keys (comma-separated)

Logging Options:

  • --log-file PATH - Write logs to file
  • --log-level LEVEL - Set log level (0-4)

Configuration Options:

  • --config PATH - Load configuration from TOML file
  • --config-create [PATH] - Create default config file and exit

Usage Examples

Server Examples

Basic Server:

./ascii-chat server

Server with Authentication:

./ascii-chat server --key ~/.ssh/id_ed25519 --client-keys github:user1,github:user2

Server with Custom Port:

./ascii-chat server --port 8080

Server with Logging:

./ascii-chat server --log-file /var/log/ascii-chat.log --log-level 1

Client Examples

Basic Client:

./ascii-chat client

Connect to Remote Server:

./ascii-chat client 192.168.1.100:27224

Client with Audio and Color:

./ascii-chat client --audio --color

Client with Custom Webcam:

./ascii-chat client --webcam 1 --webcam-flip

Snapshot Mode:

./ascii-chat client --snapshot --snapshot-delay 5

Client with Server Verification:

./ascii-chat client --server-key github:zfogg

Programmatic API

Parse Options:

options_t opts;
asciichat_error_t err = parse_options(argc, argv, &opts);
if (err != ASCIICHAT_OK) {
return err;
}

Error Handling:

asciichat_error_t err = parse_options(argc, argv, &opts);
if (err != ASCIICHAT_OK) {
asciichat_error_context_t ctx;
if (HAS_ERRNO(&ctx)) {
log_error("Invalid options: %s", ctx.context_message);
}
return err;
}

Help System

Show Help:

./ascii-chat --help
./ascii-chat server --help
./ascii-chat client --help

Help Output:

  • Lists all available options
  • Shows default values
  • Provides usage examples
  • Displays mode-specific options
See also
Configuration for configuration files and precedence
options.h
config.h