ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
src/client/connection.c
Go to the documentation of this file.
2#include <ascii-chat/otel.h>
4#include "server.h"
5#include "audio.h"
6#include "crypto.h"
7#include "protocol_adapter.h"
8#include "../main.h"
11#include <libplatform/system.h>
12#include <ascii-chat/log/log.h>
14#include <string.h>
16
17static void client_sync_entries(client_connection_t *ctx, bool add) {
18 if (add)
19 NAMED_REGISTER_ATOMIC(&ctx->display_first_frame, "client.display_first_frame", NULL);
20 else
22 if (add)
23 NAMED_REGISTER_ATOMIC(&ctx->lifecycle.state, "client.lifecycle.state", NULL);
24 else
26 if (add)
27 NAMED_REGISTER_ATOMIC(&ctx->stop_requested, "client.stop_requested", NULL);
28 else
30 if (add)
31 NAMED_REGISTER_ATOMIC(&ctx->connection_active, "client.connection_active", NULL);
32 else
34 if (add)
35 NAMED_REGISTER_ATOMIC(&ctx->remote_error, "client.remote_error", NULL);
36 else
38 if (add)
39 NAMED_REGISTER_ATOMIC(&ctx->connection_lost, "client.connection_lost", NULL);
40 else
42 if (add)
43 NAMED_REGISTER_ATOMIC(&ctx->data_thread_exited, "client.data_thread_exited", NULL);
44 else
46 if (add)
47 NAMED_REGISTER_ATOMIC(&ctx->frames_rendered, "client.frames_rendered", NULL);
48 else
50 if (add)
51 NAMED_REGISTER_ATOMIC(&ctx->snapshot_first_frame_time_ns, "client.snapshot_first_frame_time_ns", NULL);
52 else
54 if (add)
55 NAMED_REGISTER_ATOMIC(&ctx->capture_thread_exited, "client.capture_thread_exited", NULL);
56 else
58 if (add)
59 NAMED_REGISTER_ATOMIC(&ctx->audio_capture_thread_exited, "client.audio_capture_thread_exited", NULL);
60 else
62 if (add)
63 NAMED_REGISTER_ATOMIC(&ctx->audio_sender_should_exit, "client.audio_sender_should_exit", NULL);
64 else
66 if (add)
67 NAMED_REGISTER_ATOMIC(&ctx->audio_sender_exited, "client.audio_sender_exited", NULL);
68 else
70 if (add)
71 NAMED_REGISTER_ATOMIC(&ctx->ping_thread_exited, "client.ping_thread_exited", NULL);
72 else
74}
75
76static void client_interrupt(void *context);
77static asciichat_error_t client_join(void *context);
78static void client_release(void *context);
79
81#ifndef NDEBUG
82 const char *failure = SAFE_GETENV("ASCII_CHAT_TEST_CLIENT_FAIL_STAGE");
83 if (failure && strcmp(failure, stage) == 0) {
84 log_debug("Injected client startup failure after %s", stage);
85 return SET_ERRNO(ERROR_THREAD, "Injected client startup failure after %s", stage);
86 }
87#else
88 (void)stage;
89#endif
90 return ASCIICHAT_OK;
91}
92
94 if (!out) {
95 return SET_ERRNO(ERROR_INVALID_STATE, "Missing client connection output");
96 }
98 if (!*out)
99 return SET_ERRNO(ERROR_MEMORY, "Could not allocate client connection");
100 client_connection_t *connection = *out;
101 client_sync_entries(connection, true);
103 (session_connection_ops_t){client_interrupt, client_join, client_release}, connection);
104 connection->crypto_mode = ACIP_CRYPTO_ENCRYPT;
106 if (result == ASCIICHAT_OK && server_connection_init(connection) != 0)
107 result = ERROR_THREAD;
108 if (result == ASCIICHAT_OK)
109 result = client_connection_checkpoint("send-mutex");
110 if (result == ASCIICHAT_OK) {
111 connection->client_worker_pool = thread_pool_create("client_connection");
112 if (!connection->client_worker_pool)
113 result = SET_ERRNO(ERROR_THREAD, "Could not create connection worker pool");
114 }
115 if (result == ASCIICHAT_OK)
116 result = client_connection_checkpoint("worker-pool");
117 if (result != ASCIICHAT_OK) {
119 if (cleanup != ASCIICHAT_OK)
120 return cleanup;
121 }
122 if (result == ASCIICHAT_OK)
123 log_info("CLIENT_CONNECTION_CREATED: workers=0 frames=0");
124 return result;
125}
126
127static void client_detach(void *context) {
128 client_connection_t *connection = context;
129 atomic_ptr_store(&connection->transport, NULL);
130 connection->crypto_ctx = NULL;
131}
132
133static asciichat_error_t client_prepare(network_connection_t *network, const connection_endpoint_t *endpoint,
134 const char *password, void *context) {
135 client_connection_t *connection = context;
136 atomic_ptr_store(&connection->transport, network_connection_transport(network));
137 server_connection_set_ip(connection, endpoint->host);
138 server_connection_set_port(connection, endpoint->port);
139 asciichat_error_t result = client_crypto_init(connection, password);
140 return result;
141}
142
144 char *password, size_t password_capacity) {
145 ASCII_OTEL_SCOPE(CONNECTION_ATTEMPT);
146 if (!password || password_capacity < PASSWORD_BUFFER_SIZE)
147 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing session password storage");
148 if (!connection || atomic_load_int(&connection->lifecycle.state) != SESSION_CONNECTION_STARTING) {
149 return SET_ERRNO(ERROR_INVALID_STATE, "Connection is not ready for startup");
150 }
151 network_connect_config_t quic_config = {.timeout_ms = 10000,
152 .tls_ca_file = GET_OPTION(tls_ca_file)[0] ? GET_OPTION(tls_ca_file) : NULL,
153 .quic_audio_datagrams = GET_OPTION(quic_audio_datagrams),
154 .quic_visual_datagrams = GET_OPTION(quic_visual_datagrams)};
155 const network_connect_config_t *connect_config = address && strncmp(address, "quic://", 7) == 0 ? &quic_config : NULL;
156 asciichat_error_t result =
157 session_connection_establish("client", address, port, connect_config, &connection->network, password,
158 password_capacity, client_prepare, client_detach, connection);
159 atomic_ptr_store(&connection->transport, network_connection_transport(connection->network));
160 connection->crypto_ctx = connection->network ? network_connection_handshake(connection->network) : NULL;
161 if (result != ASCIICHAT_OK)
162 return result;
163 if (should_exit())
164 return SET_ERRNO(ERROR_NETWORK, "Connection startup interrupted");
165 server_connection_set_transport(connection, atomic_ptr_load(&connection->transport));
166 result = client_connection_checkpoint("transport");
167 if (result != ASCIICHAT_OK)
168 return result;
169 result = client_protocol_start(connection);
170 if (result != ASCIICHAT_OK)
171 return result;
172 if (atomic_load_bool(&connection->stop_requested))
173 return SET_ERRNO(ERROR_NETWORK, "Connection stopped during startup");
174 result = session_connection_start(&connection->lifecycle);
175 if (result != ASCIICHAT_OK)
176 return result;
177 log_info("CLIENT_CONNECTION_RUNNING");
178 return ASCIICHAT_OK;
179}
180
182 if (connection)
184}
185
186static void client_interrupt(void *context) {
187 client_connection_t *connection = context;
188 atomic_store_bool(&connection->stop_requested, true);
189 server_connection_shutdown(connection);
190}
191
195
196static asciichat_error_t client_join(void *context) {
197 client_connection_t *connection = context;
198 log_debug("CLIENT_CONNECTION_IO_INTERRUPTED");
199 audio_sender_cleanup(connection);
200 audio_stop_thread(connection);
201 audio_context_t *audio = connection->audio_context;
202 if (audio && audio->initialized)
203 audio_stop_duplex(audio);
204
205 /* Pool entries are retained on join failure. Nothing below may free a
206 * dependency until every worker has actually joined. */
207 if (connection->client_worker_pool) {
209 if (result != ASCIICHAT_OK)
210 return result;
211 }
212 log_info("CLIENT_CONNECTION_STOPPED: workers=0 frames=%llu",
213 (unsigned long long)atomic_load_u64(&connection->frames_rendered));
214 return ASCIICHAT_OK;
215}
216
217static void client_release(void *context) {
218 client_connection_t *connection = context;
219 audio_context_t *audio = connection->audio_context;
220 audio_sender_finalize(connection);
222 if (audio && audio->initialized) {
223 audio_set_pipeline(audio, NULL);
226 audio->capture_media_source = NULL;
227 }
229 connection->audio_pipeline = NULL;
231 connection->video_encoder = NULL;
234 /* The shared owner drains callbacks before releasing transport and security. */
235 log_debug("CLIENT_CONNECTION_TRANSPORT_CLEANUP");
237 log_debug("CLIENT_CONNECTION_TRANSPORT_RELEASED");
238 atomic_ptr_store(&connection->transport, NULL);
239 connection->crypto_ctx = NULL;
240}
241
243 if (!owner || !*owner)
244 return ASCIICHAT_OK;
245 client_connection_t *connection = *owner;
246 asciichat_error_t result = client_connection_stop(connection);
247 if (result != ASCIICHAT_OK)
248 return result;
249 if (connection->client_worker_pool)
251 if (connection->send_mutex_initialized)
252 mutex_destroy(&connection->send_mutex);
253 log_info("CLIENT_CONNECTION_RELEASED");
254 client_sync_entries(connection, false);
255 SAFE_FREE(*owner);
256 *owner = NULL;
257 return ASCIICHAT_OK;
258}
#define SET_ERRNO(code,...)
void buffer_workspace_destroy(buffer_workspace_t *workspace)
Definition buffer.c:374
void client_audio_pipeline_destroy(client_audio_pipeline_t *pipeline)
Destroy a client audio pipeline.
void session_capture_set_audio_context(session_capture_ctx_t *ctx, void *audio_ctx)
Set the audio context for playback control.
Named object registry for debugging — log identifiable resource names.
void h265_encoder_destroy(h265_encoder_t *encoder)
Definition encoder.c:179
asciichat_error_t audio_stop_duplex(audio_context_t *ctx)
Stop full-duplex audio.
void audio_flush_playback_buffers(audio_context_t *ctx)
Flush all audio buffers to synchronize with seek operations.
void audio_set_pipeline(audio_context_t *ctx, void *pipeline)
Set audio pipeline for echo cancellation.
void audio_ring_buffer_clear(audio_ring_buffer_t *rb)
Clear all audio samples from ring buffer.
unsigned short uint16_t
Definition common.h:61
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_GETENV(name)
Definition common.h:449
#define SAFE_CALLOC(count, size, cast)
Definition common.h:275
#define NAMED_REGISTER_ATOMIC(a, name, parent_ptr)
Register an atomic_t with automatic format specifier.
#define NAMED_UNREGISTER(ptr)
Unregister a pointer.
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ERROR_INVALID_STATE
@ ERROR_NETWORK
Definition error_codes.h:72
@ ERROR_MEMORY
Definition error_codes.h:51
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
@ ERROR_THREAD
#define log_info(...)
Log an INFO message.
Definition log/log.h:608
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
#define ACIP_CRYPTO_ENCRYPT
Encryption only (no identity verification).
Definition packet.h:1139
#define GET_OPTION(field)
Safely get a specific option field (lock-free read).
Definition options.h:1336
🔊 Audio Capture and Playback Interface for ascii-chat
Transport abstraction layer for ACIP protocol.
struct network_connection network_connection_t
void network_connection_destroy(network_connection_t **owner)
crypto_handshake_context_t * network_connection_handshake(network_connection_t *c)
acip_transport_t * network_connection_transport(network_connection_t *c)
📝 Logging API with multiple log levels and terminal output control
bool should_exit(void)
Definition main.c:249
Global application exit and signal handling API.
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
#define ASCII_OTEL_SCOPE(id)
Definition otel.h:72
Password prompting utilities with secure input and formatting.
asciichat_error_t client_protocol_start(client_connection_t *connection)
static bool size_t gotel_query_t * out
Definition queries.h:8
#define PASSWORD_BUFFER_SIZE
Password input buffer size (256 bytes).
Definition sizes.h:158
void audio_sender_cleanup(client_connection_t *connection)
Cleanup async audio sender.
void audio_stop_thread(client_connection_t *connection)
Stop audio capture thread.
void audio_sender_finalize(client_connection_t *connection)
ascii-chat Client Audio Processing Management Interface
asciichat_error_t client_connection_stop(client_connection_t *connection)
asciichat_error_t client_connection_destroy(client_connection_t **owner)
void client_connection_request_stop(client_connection_t *connection)
asciichat_error_t client_connection_start(client_connection_t *connection, const char *address, uint16_t port, char *password, size_t password_capacity)
asciichat_error_t client_connection_checkpoint(const char *stage)
asciichat_error_t client_connection_create(client_connection_t **out)
struct client_connection client_connection_t
asciichat_error_t client_crypto_init(client_connection_t *connection, const char *password)
void server_connection_set_transport(client_connection_t *connection, acip_transport_t *transport)
Set ACIP transport instance from connection fallback.
void server_connection_shutdown(client_connection_t *connection)
Interrupt connection I/O from normal thread context.
int server_connection_init(client_connection_t *connection)
Initialize the server connection management subsystem.
void server_connection_set_port(client_connection_t *connection, int port)
Set the server port.
void server_connection_set_ip(client_connection_t *connection, const char *ip)
Set the server IP address.
ascii-chat Client Server Connection Management Interface
asciichat_error_t session_connection_stop(session_connection_t *connection)
void session_connection_request_stop(session_connection_t *connection)
void session_connection_init(session_connection_t *connection, session_connection_ops_t ops, void *context)
asciichat_error_t session_connection_start(session_connection_t *connection)
asciichat_error_t session_connection_establish(const char *name, const char *address, uint16_t port, const network_connect_config_t *config, network_connection_t **owner, char *password, size_t capacity, network_connection_prepare_fn prepare, void(*detach)(void *), void *context)
@ SESSION_CONNECTION_STARTING
🔤 String Manipulation and Shell Escaping Utilities
When code needs to work with audio device info, it keeps index, max_input_channels,...
bool initialized
True if context has been initialized.
void * capture_media_source
Borrowed local media audio; configure before starting duplex.
audio_ring_buffer_t * capture_buffer
OLD: Worker → Network (will become processed_capture_rb).
network_connection_t * network
client_audio_pipeline_t * audio_pipeline
crypto_handshake_context_t * crypto_ctx
thread_pool_t * client_worker_pool
atomic_t snapshot_first_frame_time_ns
buffer_workspace_t encoded_video_workspace
session_capture_ctx_t * capture_context
atomic_t audio_capture_thread_exited
buffer_workspace_t decoded_ascii_workspace
h265_encoder_t *volatile video_encoder
audio_context_t * audio_context
session_connection_t lifecycle
For network connect config, this is the live record that ties timeout_ms, callback_data,...
void thread_pool_destroy(thread_pool_t *pool)
Destroy a thread pool.
thread_pool_t * thread_pool_create(const char *pool_name)
Create a new thread pool (long-lived threads mode).
Definition thread_pool.c:92
asciichat_error_t thread_pool_stop_all(thread_pool_t *pool)
Stop all threads in the pool in stop_id order.