ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
Core Infrastructure

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

Modules

 Common Definitions
 ⚙️ Code shared throughout the library
 
 Buffer Sizes
 Standard buffer size constants.
 
 Error and Exit Codes
 Error codes and status values for ascii-chat.
 
 Application Limits
 Limits for clients, frame rates, display names, and other constraints.
 
 Logging Rate Limits
 Standard rate limits for log_*_every() macros.
 
 Protocol Constants
 Protocol version, feature flags, compression, and frame constants.
 
 Shutdown System
 Clean shutdown detection without library accessing application state.
 
 String Literal Constants
 Common string literal constants for boolean and value comparisons.
 
 Logging System
 📝 Logging API with multiple log levels and terminal output control
 
 Config Module
 The toml text file configuration file module.
 
 Options Module
 ⚙️ Command-line option parsing with unified mode detection, builder API, and RCU-based thread-safe access
 
 Validation Helpers
 Reusable validation macros for protocol handlers.
 
 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 and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_LOGGING_INIT
Definition error_codes.h:59
@ ERROR_INVALID_STATE
@ ERROR_CRYPTO_HANDSHAKE
Definition error_codes.h:99
@ ERROR_NETWORK_CONNECT
Definition error_codes.h:79
@ ERROR_CRYPTO_KEY
Definition error_codes.h:97
@ ERROR_SIGNAL_INTERRUPT
@ ERROR_COMPRESSION
@ ERROR_NETWORK_BIND
Definition error_codes.h:78
@ ERROR_NETWORK
Definition error_codes.h:77
@ ERROR_PLATFORM_INIT
Definition error_codes.h:60
@ ERROR_WEBCAM_IN_USE
Definition error_codes.h:65
@ ERROR_CRYPTO_VERIFICATION
@ ERROR_RESOURCE_EXHAUSTED
@ ERROR_BUFFER_FULL
@ ERROR_AUDIO
Definition error_codes.h:67
@ ERROR_NETWORK_PROTOCOL
Definition error_codes.h:81
@ ERROR_DECOMPRESSION
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_GENERAL
Definition error_codes.h:52
@ ERROR_STRING
@ ERROR_NETWORK_SIZE
Definition error_codes.h:82
@ ERROR_CRYPTO_AUTH
Definition error_codes.h:98
@ ERROR_WEBCAM_PERMISSION
Definition error_codes.h:66
@ ERROR_DISPLAY
@ ERROR_CRYPTO_INIT
Definition error_codes.h:58
@ ERROR_INVALID_FRAME
@ ERROR_CONFIG
Definition error_codes.h:57
@ ERROR_CRYPTO
Definition error_codes.h:96
@ ERROR_SIGNAL_CRASH
@ ERROR_USAGE
Definition error_codes.h:53
@ ERROR_INVALID_PARAM
@ ERROR_BUFFER_OVERFLOW
@ ERROR_AUDIO_IN_USE
Definition error_codes.h:68
@ ERROR_TERMINAL
Definition error_codes.h:69
@ ERROR_ASSERTION_FAILED
@ ERROR_NETWORK_TIMEOUT
Definition error_codes.h:80
@ ERROR_WEBCAM
Definition error_codes.h:64
@ ERROR_BUFFER
@ ERROR_THREAD
@ ERROR_FORMAT

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
unsigned long long uint64_t
Definition common.h:59
Error context structure.

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;
}
#define SET_ERRNO_SYS(code, context_msg,...)
Set error code with custom message and system error context, returning the error code.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
@ ERROR_NOT_FOUND

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;
}
#define INVALID_SOCKET_VALUE
Invalid socket value (POSIX: -1)
Definition socket.h:278
int errno
int socket_t

HAS_ERRNO: Check if error occurred

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;
}
#define HAS_ERRNO(var)
Check if an error occurred and get full context.
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
char * context_message
Optional custom message (dynamically allocated, owned by system)
const char * file
Source file where error occurred (NULL in release builds)
int line
Line number where error occurred (0 in release builds)

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
}
#define PRINT_ERRNO_CONTEXT(context)

CLEAR_ERRNO: Clear error state

CLEAR_ERRNO(); // Resets thread-local error state
#define CLEAR_ERRNO()
Clear the current error state.

GET_ERRNO: Get current error code

asciichat_error_t current_error = GET_ERRNO();
if (current_error != ASCIICHAT_OK) {
// Handle error
}
#define GET_ERRNO()
Get current error code (0 if no 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
#define SAFE_MALLOC(size, cast)
Definition common.h:264
unsigned char uint8_t
Definition common.h:56

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;
}
#define SAFE_CALLOC(count, size, cast)
Definition common.h:274

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;
#define SAFE_REALLOC(ptr, size, cast)
Definition common.h:284
#define SAFE_FREE(ptr)
Definition common.h:376

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
Logging levels enumeration.
Definition types.h:29
#define LOG_DEBUG
Definition types.h:39
#define LOG_FATAL
Definition types.h:43
#define LOG_ERROR
Definition types.h:42
#define LOG_WARN
Definition types.h:41
#define LOG_INFO
Definition types.h:40

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
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_fatal(...)
Log a FATAL message.
Definition log/log.h:599
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548

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
#define FATAL(code,...)
Exit with error code and custom message, with stack trace in debug builds.
Definition common.h:158

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);
#define log_info_every(interval_us, fmt,...)
Rate-limited INFO logging.
Definition log/log.h:705
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
Definition log/log.h:702
#define log_warn_every(interval_us, fmt,...)
Rate-limited WARN logging.
Definition log/log.h:708

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
// Change log level at runtime
// Enable/disable color output
void log_set_color(bool enable);
// Cleanup logging
void log_destroy(void);
void log_destroy(void)
Destroy the logging system and close log file.
Definition log/log.c:624
void log_set_level(log_level_t level)
Set the minimum log level.
Definition log/log.c:691
void log_init(const char *filename, log_level_t level, bool force_stderr, bool use_mmap)
Initialize the logging system.
Definition log/log.c:533
#define log_file(...)
File-only logging - writes to log file only, no stderr output.
Definition log/log.h:646

Usage:

// Log to file and stderr
log_init("/tmp/ascii-chat.log", LOG_INFO);
// Later: increase verbosity
// 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
unsigned short uint16_t
Definition common.h:57
unsigned int uint32_t
Definition common.h:58

Boolean (C99 style):

#include <stdbool.h>
bool, true, false
ClangTool/LibTooling compatibility shim for stdbool.h.
#define bool
Definition stdbool.h:61

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) {
// Load configuration
options_t opts;
err = config_load(&opts, config_file);
if (err != ASCIICHAT_OK) {
if (HAS_ERRNO(&ctx)) {
log_error("Config load failed: %s", ctx.context_message);
}
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)
Initialize audio context and PortAudio.
crypto_result_t crypto_init(crypto_context_t *ctx)
Initialize libsodium and crypto context.
void crypto_destroy(crypto_context_t *ctx)
Cleanup crypto context with secure memory wiping.
Audio context for full-duplex capture and playback.
Cryptographic context structure.
Consolidated options structure.

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);
TOML configuration file support for ascii-chat.
int height
Terminal height in characters (int for OPTION_TYPE_INT compat)
int max_clients
Maximum concurrent clients (server only)
int fps
Target framerate (1-144, default: 60)
int width
Terminal width in characters (int for OPTION_TYPE_INT compat)
char address[256]
Server address (client) or bind address (server)

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) {
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;
// 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) {
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;
}
@ MODE_SERVER
Server mode - network server options.
int main(int argc, char *argv[])
Definition main.c:472
char config_file[256]
Config file path (–config)

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_color = true;
}
#define AUDIO_CHANNELS
Number of audio channels (1 = mono)
log_level_t log_level
Log level threshold.
int color
Color setting (COLOR_SETTING_AUTO/TRUE/FALSE)
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:

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 Characteristics

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) {
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