ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
main.c File Reference

ascii-chat Client Main Entry Point More...

Go to the source code of this file.

Data Structures

struct  client_session_state_t
 Client connection session state for session_client_like integration. More...
 

Functions

int client_main (void)
 Client mode entry point for unified binary.
 

Variables

thread_pool_t * g_client_worker_pool = NULL
 Global client worker thread pool.
 
app_client_t * g_client = NULL
 Global client application context.
 
struct webrtc_peer_manager * g_peer_manager = NULL
 Global WebRTC peer manager (legacy compatibility)
 

Detailed Description

ascii-chat Client Main Entry Point

This module serves as the main entry point for the ascii-chat client application. It orchestrates the entire client lifecycle including initialization, connection management, and the primary event loop that manages reconnection logic.

Architecture Overview

The client follows a modular threading architecture:

  • Main thread: Connection management and event coordination
  • Data reception thread: Handles incoming packets from server
  • Ping thread: Maintains connection keepalive
  • Webcam capture thread: Captures and transmits video frames
  • Audio capture thread: Captures and transmits audio data (optional)

Connection Management

The client implements robust reconnection logic with exponential backoff:

  1. Initial connection attempt
  2. On connection loss, attempt reconnection with increasing delays
  3. Maximum delay cap to prevent excessive wait times
  4. Clean thread lifecycle management across reconnections

Thread Lifecycle

Each connection cycle follows this pattern:

  1. Connection Establishment: Socket creation and server handshake
  2. Thread Spawning: Start all worker threads for the connection
  3. Active Monitoring: Monitor connection health and thread status
  4. Connection Loss Detection: Detect broken connections via thread exit
  5. Cleanup Phase: Join all threads and reset connection state
  6. Reconnection Cycle: Repeat from step 1 unless shutdown requested

Integration Points

  • server.c: Connection establishment and socket management
  • protocol.c: Initial capability negotiation and join packets
  • display.c: Terminal initialization and control
  • capture.c: Media capture subsystem initialization
  • audio.c: Audio subsystem initialization (if enabled)
  • keepalive.c: Ping thread management

Error Handling

The main loop implements graceful error recovery:

  • Fatal initialization errors cause immediate exit
  • Network errors trigger reconnection attempts
  • Signal handling for graceful shutdown (SIGINT, SIGWINCH)
  • Resource cleanup on all exit paths

Platform Compatibility

Uses platform abstraction layer for:

  • Socket operations and network initialization
  • Thread management and synchronization
  • Signal handling differences between Unix and Windows
  • Terminal I/O and control sequences
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
October 2025
Version
2.0

Definition in file client/main.c.

Function Documentation

◆ client_main()

int client_main ( void  )

Client mode entry point for unified binary.

This function implements the complete client lifecycle including:

  • Client-specific subsystem initialization (display, capture, audio)
  • Server connection establishment with exponential backoff reconnection
  • Main event loop coordinating connection state and thread lifecycle
  • Media streaming (webcam frames and optional audio)
  • Frame display to terminal
  • Graceful shutdown and resource cleanup

Lifecycle Overview

The client follows this high-level state flow:

  1. Initialize: Set up display, capture, audio subsystems
  2. Connect Loop:
    • Establish TCP connection to server
    • Perform cryptographic handshake
    • Spawn worker threads (data reception, capture, keepalive, audio)
    • Monitor connection health
    • Detect connection loss via thread exit or socket errors
  3. Cleanup: Stop threads, close socket, reset terminal
  4. Reconnect: Exponential backoff, retry (unless fatal auth error)
  5. Exit: On user signal (SIGINT) or fatal error

Connection Failure Handling

Transient Errors (retryable):

  • Connection refused, timeout, DNS failure → exponential backoff retry
  • Network temporarily unavailable → wait and retry

Fatal Errors (no retry):

  • Authentication failed (CONNECTION_ERROR_AUTH_FAILED) → exit immediately
  • Host key verification failed (CONNECTION_ERROR_HOST_KEY_FAILED) → exit immediately

Thread Management

The client spawns several worker threads coordinated by the main thread:

  • Data Reception Thread: Receives and processes server packets
  • Webcam Capture Thread: Captures frames, transmits to server
  • Ping/Keepalive Thread: Sends periodic pings, detects timeouts
  • Audio Threads (optional): Captures/plays audio with Opus encoding

All threads check the global g_should_exit flag and exit cleanly on shutdown. Main thread waits for all threads to exit before reconnection or final cleanup.

Options

Options are already parsed by the main dispatcher (src/main.c) before this function is called. They are available via the global options API (GET_OPTION, etc.).

Key options affecting behavior:

  • opt_address: Server address or session string
  • opt_port: Server port (default 27224)
  • opt_audio_enabled: Enable audio capture/playback
  • opt_snapshot: Capture single frame and exit
  • opt_client_key, opt_server_key: SSH keys for authentication
Returns
Exit code:
  • 0: Success (clean exit or snapshot completed)
  • 1: Initialization/configuration error
  • 2: Connection fatal error (auth failed)
  • 130: Signal interrupt (SIGINT/Ctrl+C)
Example
# Invoked by dispatcher after options are parsed:
ascii-chat client --address localhost --audio
# Dispatcher (src/main.c) handles:
# - Option parsing
# - Log initialization
# - Signal handler setup
# Then calls client_main() which handles:
# - Subsystem init (display, capture, audio)
# - Connection loop with reconnection logic
# - Thread coordination
# - Graceful shutdown
See also
topic_client "Client Architecture Overview"
topic_client_main "Client Lifecycle Details"
topic_client_connection "Connection Management"
topic_client_protocol "Packet Processing"

Definition at line 630 of file client/main.c.

630 {
631 log_debug("client_main() starting");
632
633 // Register client crypto callbacks for TCP connection path
634 // The TCP path in lib/network/connection_attempt.c relies on these callbacks
635 // to handle cryptographic operations and key exchange
636 static const app_callbacks_t client_crypto_callbacks = {
638 .signal_exit = signal_exit,
639 .platform_get_monotonic_time_us = platform_get_monotonic_time_us,
640 .platform_sleep_ms = platform_sleep_ms,
641 .server_connection_set_ip = server_connection_set_ip,
642 .server_connection_set_port = server_connection_set_port,
643 .client_crypto_set_mode = client_crypto_set_mode,
644 .client_crypto_init = client_crypto_init,
645 .client_crypto_handshake = client_crypto_handshake,
646 .crypto_client_is_ready = crypto_client_is_ready,
647 .crypto_client_get_context = crypto_client_get_context,
648 };
649 app_callbacks_register(&client_crypto_callbacks);
650
651 // Initialize client-specific systems (NOT shared with session_client_like)
652 // This includes: thread pool, display layer, app client context, server connection
653 int init_result = initialize_client_systems();
654 if (init_result != 0) {
655#ifndef NDEBUG
656 // Debug builds: automatically fall back to test pattern if webcam is in use
657 if (init_result == ERROR_WEBCAM_IN_USE && !GET_OPTION(test_pattern)) {
658 log_warn("Webcam is in use - automatically falling back to test pattern mode (debug build only)");
659
660 // Enable test pattern mode via RCU update
661 asciichat_error_t update_result = options_set_bool("test_pattern", true);
662 if (update_result != ASCIICHAT_OK) {
663 log_error("Failed to update options for test pattern fallback");
664 FATAL(init_result, "%s", asciichat_error_string(init_result));
665 }
666
667 // Retry initialization with test pattern enabled
668 init_result = initialize_client_systems();
669 if (init_result != 0) {
670 log_error("Failed to initialize even with test pattern fallback");
671 webcam_print_init_error_help(init_result);
672 FATAL(init_result, "%s", asciichat_error_string(init_result));
673 }
674 log_debug("Successfully initialized with test pattern fallback");
675
676 // Clear the error state since we successfully recovered
677 CLEAR_ERRNO();
678 } else
679#endif
680 {
681 // Release builds or other errors: print help and exit
682 if (init_result == ERROR_WEBCAM || init_result == ERROR_WEBCAM_IN_USE || init_result == ERROR_WEBCAM_PERMISSION) {
683 webcam_print_init_error_help(init_result);
684 FATAL(init_result, "%s", asciichat_error_string(init_result));
685 }
686 // For other errors, just exit with the error code
687 return init_result;
688 }
689 }
690
691 // Register cleanup function for graceful shutdown
692 (void)atexit(shutdown_client);
693
694 // Register client interrupt callback (socket shutdown on SIGTERM/Ctrl+C)
695 // Global signal handlers (SIGTERM, SIGPIPE, Ctrl+C) are set up in setup_signal_handlers() in src/main.c
697
698#ifndef _WIN32
699 // Register SIGWINCH for terminal resize handling (client-specific, not in framework)
700 platform_signal(SIGWINCH, client_handle_sigwinch);
701#endif
702
703 /* ========================================================================
704 * Client-Specific: LAN/Session Discovery
705 * ========================================================================
706 * This phase discovers the server to connect to via:
707 * - LAN discovery (mDNS)
708 * - Session string lookup (ACDS)
709 * - Direct address/port
710 */
711
712 // LAN Discovery: If --scan flag is set, discover servers on local network
713 const options_t *opts = options_get();
714 if (opts && opts->lan_discovery &&
715 (opts->address[0] == '\0' || is_localhost_ipv4(opts->address) || strcmp(opts->address, "localhost") == 0)) {
716 log_debug("LAN discovery: --scan flag set, querying for available servers");
717
718 ui_mdns_config_t lan_config;
719 memset(&lan_config, 0, sizeof(lan_config));
720 lan_config.timeout_ms = 2 * MS_PER_SEC_INT; // Wait up to 2 seconds for responses
721 lan_config.max_servers = 20; // Support up to 20 servers on LAN
722 lan_config.quiet = true; // Quiet during discovery, TUI will show status
723
724 int discovered_count = 0;
725 ui_mdns_server_t *discovered_servers = ui_mdns_query(&lan_config, &discovered_count);
726
727 // Use TUI for server selection
728 int selected_index = ui_mdns_select(discovered_servers, discovered_count);
729
730 if (selected_index < 0) {
731 // User cancelled or no servers found
732 if (discovered_count == 0) {
733 // No servers found - log message and prevent any further output
734 // Lock the terminal so other threads can't write and our error
735 // will be the last message
737
738 // Log single message with embedded newlines to prevent multiple log entries
739 log_error("No ascii-chat servers found on the local network.\nUse 'ascii-chat client <address>' to connect "
740 "manually.");
741
742 // Exit without cleanup
744 }
745 // User cancelled (had servers to choose from but pressed cancel)
746 log_debug("LAN discovery: User cancelled server selection");
747 if (discovered_servers) {
748 ui_mdns_free_results(discovered_servers);
749 }
750 return 1; // User cancelled
751 }
752
753 // Update options with discovered server's address and port
754 ui_mdns_server_t *selected = &discovered_servers[selected_index];
755 const char *selected_address = ui_mdns_get_best_address(selected);
756
757 // We need to modify options, but they're immutable via RCU
758 // Create a new options copy with updated address/port
759 options_t *opts_new = SAFE_MALLOC(sizeof(options_t), options_t *);
760 if (opts_new) {
761 memcpy(opts_new, opts, sizeof(options_t));
762 SAFE_STRNCPY(opts_new->address, selected_address, sizeof(opts_new->address));
763
764 opts_new->port = (int)selected->port;
765
766 log_debug("LAN discovery: Selected server '%s' at %s:%d", selected->name, opts_new->address, opts_new->port);
767
768 // Note: In a real scenario, we'd update the global options via RCU
769 // For now, we'll use the updated values directly for connection
770 // This is a known limitation - proper RCU update requires more infrastructure
771 }
772
773 ui_mdns_free_results(discovered_servers);
774 }
775
776 // =========================================================================
777 // Client-Specific: Server Address Resolution
778 // =========================================================================
779 // Client mode supports:
780 // 1. Direct address/port (--address HOST --port PORT) - handled by options
781 // 2. LAN discovery (--scan) - handled above
782 // 3. WebSocket URL (direct ws:// or wss:// connection string)
783 //
784 // Note: Session string discovery via ACDS is handled by discovery mode only.
785 // Client mode does NOT use ACDS or session strings.
786
787 const char *discovered_address = NULL;
788 int discovered_port = 0;
789
790 // Check if user provided a WebSocket URL as the server address
791 const options_t *opts_websocket = options_get();
792 const char *provided_address = opts_websocket && opts_websocket->address[0] != '\0' ? opts_websocket->address : NULL;
793
794 if (provided_address && url_is_websocket(provided_address)) {
795 // Direct WebSocket connection
796 log_debug("Client: Direct WebSocket URL: %s", provided_address);
797 discovered_address = provided_address;
798 discovered_port = 0; // Port is embedded in the URL
799 }
800
801 if (discovered_port > 0) {
802 log_debug("Client: discovered_address=%s, discovered_port=%d", discovered_address ? discovered_address : "NULL",
803 discovered_port);
804 } else {
805 log_debug("Client: discovered_address=%s", discovered_address ? discovered_address : "NULL");
806 }
807
808 // Store discovered address/port in session state for client_run() callback
809 static char address_storage[BUFFER_SIZE_SMALL];
810 if (discovered_address) {
811 SAFE_STRNCPY(address_storage, discovered_address, sizeof(address_storage));
812 g_client_session.discovered_address = address_storage;
813 g_client_session.discovered_port = discovered_port;
814 } else {
815 const options_t *opts_fallback = options_get();
816 if (opts_fallback && opts_fallback->address[0] != '\0') {
817 SAFE_STRNCPY(address_storage, opts_fallback->address, sizeof(address_storage));
818 g_client_session.discovered_address = address_storage;
819 g_client_session.discovered_port = opts_fallback->port;
820 } else {
821 g_client_session.discovered_address = "localhost";
822 g_client_session.discovered_port = 27224;
823 }
824 }
825
826 // Initialize connection context for first attempt
827 asciichat_error_t ctx_init_result = connection_context_init(&g_client_session.connection_ctx);
828 if (ctx_init_result != ASCIICHAT_OK) {
829 log_error("Failed to initialize connection context");
830 return 1;
831 }
832
833 /* ========================================================================
834 * Configure and Run Shared Client-Like Session Framework
835 * ========================================================================
836 * session_client_like_run() handles all shared initialization:
837 * - Terminal output management (force stderr if piped)
838 * - Keepawake system (platform sleep prevention)
839 * - Splash screen lifecycle
840 * - Media source selection (webcam, file, URL, test pattern)
841 * - FPS probing for media files
842 * - Audio initialization and lifecycle
843 * - Display context creation
844 * - Proper cleanup ordering (critical for PortAudio)
845 *
846 * Client mode provides:
847 * - client_run() callback: connection loop, protocol startup, monitoring
848 * - client_should_reconnect() callback: reconnection policy
849 * - Reconnection configuration: attempts, delay, callbacks
850 */
851
852 // Get reconnect attempts setting (-1 = unlimited, 0 = no retry, >0 = retry N times)
853 int reconnect_attempts = GET_OPTION(reconnect_attempts);
854
855 // Configure session_client_like with client-specific settings
857 .run_fn = client_run,
858 .run_user_data = NULL,
860 .discovery = NULL,
861 .custom_should_exit = NULL,
862 .exit_user_data = NULL,
863 .keyboard_handler = NULL, // Client mode: server drives display
864 .max_reconnect_attempts = reconnect_attempts,
865 .should_reconnect_callback = client_should_reconnect,
866 .reconnect_user_data = NULL,
867 .reconnect_delay_ms = 1000, // 1 second delay between reconnection attempts
868 .print_newline_on_tty_exit = false, // Server/client manages cursor
869 };
870
871 log_debug("[CLIENT_MAIN] About to call session_client_like_run() with %d attempts", reconnect_attempts);
872 asciichat_error_t session_result = session_client_like_run(&config);
873 log_debug("[CLIENT_MAIN] session_client_like_run() returned %d", session_result);
874
875 // Network client lifecycle is owned by connection_context_cleanup() after the session returns.
876
877 // Cleanup connection context
878 log_debug("[CLIENT_MAIN] About to call connection_context_cleanup()");
880 log_debug("[CLIENT_MAIN] connection_context_cleanup() returned");
881
882 // Cleanup session log buffer (used by splash screen in session_client_like)
883 // NOTE: session_log_buffer_destroy() is already called in session_client_like_run()
884 // during its cleanup phase, so we don't call it here to avoid double-destroy of mutex
885
886 log_debug("[CLIENT_MAIN] ascii-chat client shutting down");
887
888 // IMPORTANT: Stop worker threads and join them BEFORE memory report
889 // atexit(shutdown_client) won't run if interrupted by SIGTERM, so call explicitly
890 log_debug("[CLIENT_MAIN] About to call shutdown_client()");
891 fflush(stderr);
892 shutdown_client();
893 log_debug("[CLIENT_MAIN] shutdown_client() returned");
894 fflush(stderr);
895
896 // Cleanup remaining shared subsystems (buffer pool, platform, etc.)
897 log_debug("[CLIENT_MAIN] About to call asciichat_shared_destroy()");
899
900 // Log final session statistics before exit
901 // Note: Frame count is logged in protocol_stop_connection() if connection was established
902 // This is a fallback in case the connection never started
903 return (session_result == ASCIICHAT_OK) ? 0 : 1;
904}
void app_callbacks_register(const app_callbacks_t *callbacks)
asciichat_error_t session_client_like_run(const session_client_like_config_t *config)
@ SESSION_CLIENT_LIKE_KIND_CLIENT
Definition client_like.h:65
asciichat_error_t connection_context_init(connection_attempt_context_t *ctx)
Initialize connection attempt context.
void connection_context_cleanup(connection_attempt_context_t *ctx)
Cleanup connection attempt context.
#define BUFFER_SIZE_SMALL
Small buffer size (256 bytes)
void server_connection_shutdown()
Emergency connection shutdown for signal handlers.
void server_connection_set_ip(const char *ip)
Set the server IP address.
void server_connection_set_port(int port)
Set the server port.
const crypto_context_t * crypto_client_get_context(void)
Get crypto context for encryption/decryption.
int client_crypto_handshake(acip_transport_t *transport)
Perform crypto handshake with server.
void client_crypto_set_mode(uint8_t mode)
Set crypto mode for handshake (encryption + authentication)
int client_crypto_init(void)
Initialize client crypto handshake.
bool crypto_client_is_ready(void)
Check if crypto handshake is ready.
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#define FATAL(code,...)
Exit with error code and custom message, with stack trace in debug builds.
Definition common.h:158
void asciichat_shared_destroy(void)
Clean up all shared library subsystems.
Definition common.c:195
#define CLEAR_ERRNO()
Clear the current error state.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_WEBCAM_IN_USE
Definition error_codes.h:65
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_WEBCAM_PERMISSION
Definition error_codes.h:66
@ ERROR_WEBCAM
Definition error_codes.h:64
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
bool log_lock_terminal(void)
Lock terminal output for exclusive access by the calling thread.
Definition log/log.c:787
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
#define MS_PER_SEC_INT
Definition time.h:164
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
const options_t * options_get(void)
Get current options (lock-free read)
Definition rcu.c:496
asciichat_error_t options_set_bool(const char *field_name, bool value)
Definition rcu.c:969
signal_handler_t platform_signal(int sig, signal_handler_t handler)
Set a signal handler.
Definition misc.c:115
void platform_force_exit(int exit_code)
Forcefully terminate the process immediately without cleanup.
Definition misc.c:121
uint64_t platform_get_monotonic_time_us(void)
Get monotonic time in microseconds.
void platform_sleep_ms(unsigned int ms)
Sleep for a specified number of milliseconds.
bool url_is_websocket(const char *url)
Check if a URL string is a valid WebSocket URL.
Definition url.c:307
int is_localhost_ipv4(const char *ip)
Check if IPv4 address is localhost.
Definition ip.c:1299
void webcam_print_init_error_help(asciichat_error_t error_code)
Print helpful error diagnostics for webcam initialization failures.
void signal_exit(void)
Definition misc.c:129
void set_interrupt_callback(void(*callback)(void))
Definition misc.c:125
mdns_result_t * ui_mdns_get_best_address(mdns_query_t *query)
Definition misc.c:144
void ui_mdns_free_results(mdns_result_t *results)
Definition misc.c:155
mdns_query_t * ui_mdns_query(const char *service)
Definition misc.c:139
bool should_exit(void)
Definition misc.c:131
int ui_mdns_select(const ui_mdns_server_t *servers, int count)
TUI-based server selection with formatted display.
Definition misc.c:149
bool(* should_exit)(void)
const char * discovered_address
connection_attempt_context_t connection_ctx
Consolidated options structure.
bool lan_discovery
Enable LAN service discovery via mDNS (client only)
char address[256]
Server address (client) or bind address (server)
session_client_like_run_fn run_fn
Configuration for TUI discovery.
Definition ui/mdns.h:69
int max_servers
Maximum servers to collect (default: 20)
Definition ui/mdns.h:71
int timeout_ms
Maximum time to wait for responses (default: 2000)
Definition ui/mdns.h:70
bool quiet
Suppress discovery messages (default: false)
Definition ui/mdns.h:72
Discovered server information from mDNS.
Definition ui/mdns.h:57
char name[256]
Service instance name (e.g., "swift-river-canyon")
Definition ui/mdns.h:58
uint16_t port
Server port number.
Definition ui/mdns.h:60

References options_state::address, app_callbacks_register(), ASCIICHAT_OK, asciichat_shared_destroy(), BUFFER_SIZE_SMALL, CLEAR_ERRNO, client_crypto_handshake(), client_crypto_init(), client_crypto_set_mode(), connection_context_cleanup(), connection_context_init(), client_session_state_t::connection_ctx, crypto_client_get_context(), crypto_client_is_ready(), client_session_state_t::discovered_address, client_session_state_t::discovered_port, ERROR_WEBCAM, ERROR_WEBCAM_IN_USE, ERROR_WEBCAM_PERMISSION, FATAL, GET_OPTION, is_localhost_ipv4(), options_state::lan_discovery, log_debug, log_error, log_lock_terminal(), log_warn, ui_mdns_config_t::max_servers, MS_PER_SEC_INT, ui_mdns_server_t::name, options_get(), options_set_bool(), platform_force_exit(), platform_get_monotonic_time_us(), platform_signal(), platform_sleep_ms(), options_state::port, ui_mdns_server_t::port, ui_mdns_config_t::quiet, session_client_like_config::run_fn, SAFE_MALLOC, SAFE_STRNCPY, server_connection_set_ip(), server_connection_set_port(), server_connection_shutdown(), SESSION_CLIENT_LIKE_KIND_CLIENT, session_client_like_run(), set_interrupt_callback(), should_exit(), app_callbacks_t::should_exit, signal_exit(), ui_mdns_config_t::timeout_ms, ui_mdns_free_results(), ui_mdns_get_best_address(), ui_mdns_query(), ui_mdns_select(), url_is_websocket(), and webcam_print_init_error_help().

Variable Documentation

◆ g_client

app_client_t* g_client = NULL

Global client application context.

Global application client context

Central connection and application state for the client. Contains transport-agnostic state: audio, threads, display, crypto. Network-specific state (socket, connection flags) is in the active transport client.

Initialized by app_client_create() in client_main(). Destroyed by app_client_destroy() at cleanup.

Definition at line 152 of file client/main.c.

◆ g_client_worker_pool

thread_pool_t* g_client_worker_pool = NULL

Global client worker thread pool.

Global client worker thread pool

Manages all client worker threads including:

  • Data reception thread (protocol.c)
  • Webcam capture thread (capture.c)
  • Ping/keepalive thread (keepalive.c)
  • Audio capture thread (audio.c)
  • Audio sender thread (audio.c)

Definition at line 140 of file client/main.c.

Referenced by audio_sender_init(), audio_start_thread(), capture_start_thread(), keepalive_start_thread(), protocol_start_connection(), and protocol_stop_connection().

◆ g_peer_manager

struct webrtc_peer_manager* g_peer_manager = NULL

Global WebRTC peer manager (legacy compatibility)

Global WebRTC peer manager for P2P connections.

Client mode no longer uses WebRTC, but protocol.c still references this. Always NULL in client mode.

Definition at line 160 of file client/main.c.