ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
host.c File Reference

🏠 Server-side session hosting implementation More...

Go to the source code of this file.

Data Structures

struct  session_host_client_t
 Internal client record structure. More...
 
struct  session_host
 Internal session host structure. More...
 

Macros

#define SESSION_HOST_DEFAULT_MAX_CLIENTS   32
 Default maximum clients.
 

Typedefs

typedef struct session_host session_host_t
 Internal session host structure.
 

Functions

session_host_tsession_host_create (const session_host_config_t *config)
 
void session_host_destroy (session_host_t *host)
 
asciichat_error_t session_host_start (session_host_t *host)
 
void session_host_stop (session_host_t *host)
 
bool session_host_is_running (session_host_t *host)
 
uint32_t session_host_add_client (session_host_t *host, socket_t socket, const char *ip, int port)
 
uint32_t session_host_add_memory_participant (session_host_t *host)
 
asciichat_error_t session_host_inject_frame (session_host_t *host, uint32_t participant_id, const image_t *frame)
 
asciichat_error_t session_host_inject_audio (session_host_t *host, uint32_t participant_id, const float *samples, size_t count)
 
asciichat_error_t session_host_remove_client (session_host_t *host, uint32_t client_id)
 
asciichat_error_t session_host_find_client (session_host_t *host, uint32_t client_id, session_host_client_info_t *info)
 
int session_host_get_client_count (session_host_t *host)
 
int session_host_get_client_ids (session_host_t *host, uint32_t *ids, int max_ids)
 
asciichat_error_t session_host_broadcast_frame (session_host_t *host, const char *frame)
 
asciichat_error_t session_host_send_frame (session_host_t *host, uint32_t client_id, const char *frame)
 
asciichat_error_t session_host_start_render (session_host_t *host)
 
void session_host_stop_render (session_host_t *host)
 
asciichat_error_t session_host_set_client_transport (session_host_t *host, uint32_t client_id, acip_transport_t *transport)
 
acip_transport_t * session_host_get_client_transport (session_host_t *host, uint32_t client_id)
 
bool session_host_client_has_transport (session_host_t *host, uint32_t client_id)
 

Detailed Description

🏠 Server-side session hosting implementation

Implements the session host abstraction for server-side client management and session coordination.

NOTE: This is a stub implementation that provides the API structure. Full implementation will integrate with existing server code in a future phase.

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
January 2026

Definition in file host.c.

Macro Definition Documentation

◆ SESSION_HOST_DEFAULT_MAX_CLIENTS

#define SESSION_HOST_DEFAULT_MAX_CLIENTS   32

Default maximum clients.

Definition at line 43 of file host.c.

Typedef Documentation

◆ session_host_t

typedef struct session_host session_host_t

Internal session host structure.

Contains server state, client list, and callback configuration.

Function Documentation

◆ session_host_add_client()

uint32_t session_host_add_client ( session_host_t host,
socket_t  socket,
const char *  ip,
int  port 
)

Definition at line 928 of file host.c.

928 {
929 if (!host || !host->initialized) {
930 return 0;
931 }
932
933 mutex_lock(&host->clients_mutex);
934
935 // Check if we have room
936 if (host->client_count >= host->max_clients) {
937 mutex_unlock(&host->clients_mutex);
938 SET_ERRNO(ERROR_SESSION_FULL, "Maximum clients reached");
939 return 0;
940 }
941
942 // Find empty slot
943 for (int i = 0; i < host->max_clients; i++) {
944 if (!host->clients[i].active) {
945 host->clients[i].participant_type = PARTICIPANT_TYPE_NETWORK;
946 host->clients[i].client_id = host->next_client_id++;
947 host->clients[i].socket = socket;
948 if (ip) {
949 SAFE_STRNCPY(host->clients[i].ip_address, ip, sizeof(host->clients[i].ip_address));
950 }
951 host->clients[i].port = port;
952 host->clients[i].active = true;
953 host->clients[i].video_active = false;
954 host->clients[i].audio_active = false;
955 host->clients[i].connected_at = (uint64_t)time(NULL);
956 host->clients[i].transport = NULL; // No alternative transport initially
957
958 // Allocate media buffers
959 host->clients[i].incoming_video = image_new(480, 270); // Network-optimal size (HD preview)
960 host->clients[i].incoming_audio = ringbuffer_create(sizeof(float), 960 * 10); // ~200ms buffer @ 48kHz
961
962 if (!host->clients[i].incoming_video || !host->clients[i].incoming_audio) {
963 // Cleanup on allocation failure
964 if (host->clients[i].incoming_video) {
966 host->clients[i].incoming_video = NULL;
967 }
968 if (host->clients[i].incoming_audio) {
970 host->clients[i].incoming_audio = NULL;
971 }
972 host->clients[i].active = false;
973 mutex_unlock(&host->clients_mutex);
974 SET_ERRNO(ERROR_MEMORY, "Failed to allocate media buffers for client");
975 return 0;
976 }
977
978 uint32_t client_id = host->clients[i].client_id;
979 host->client_count++;
980
981 mutex_unlock(&host->clients_mutex);
982
983 // Invoke callback
984 if (host->callbacks.on_client_join) {
985 host->callbacks.on_client_join(host, client_id, host->user_data);
986 }
987
988 return client_id;
989 }
990 }
991
992 mutex_unlock(&host->clients_mutex);
993 return 0;
994}
ringbuffer_t * ringbuffer_create(size_t element_size, size_t capacity)
Definition ringbuffer.c:28
void ringbuffer_destroy(ringbuffer_t *rb)
Definition ringbuffer.c:54
ringbuffer_t * incoming_audio
Incoming audio ringbuffer (written by receive loop, read by render thread)
Definition host.c:70
char ip_address[64]
Definition host.c:56
socket_t socket
Definition host.c:55
uint32_t client_id
Definition host.c:54
participant_type_t participant_type
Definition host.c:53
uint64_t connected_at
Definition host.c:61
image_t * incoming_video
Incoming video frame buffer (for host render thread)
Definition host.c:67
struct acip_transport * transport
Alternative transport (WebRTC, WebSocket, etc.) - NULL if using socket only.
Definition host.c:64
mutex_t clients_mutex
Client list mutex.
Definition host.c:129
bool initialized
Context is initialized.
Definition host.c:159
int client_count
Current client count.
Definition host.c:123
session_host_client_t * clients
Client array.
Definition host.c:120
void * user_data
User data for callbacks.
Definition host.c:108
session_host_callbacks_t callbacks
Event callbacks.
Definition host.c:105
int max_clients
Maximum clients.
Definition host.c:93
uint32_t next_client_id
Next client ID counter.
Definition host.c:126
void image_destroy(image_t *p)
Definition video/image.c:85
image_t * image_new(size_t width, size_t height)
Definition video/image.c:36

References session_host_client_t::active, session_host_client_t::audio_active, session_host::callbacks, session_host::client_count, session_host_client_t::client_id, session_host::clients, session_host::clients_mutex, session_host_client_t::connected_at, image_destroy(), image_new(), session_host_client_t::incoming_audio, session_host_client_t::incoming_video, session_host::initialized, session_host_client_t::ip_address, session_host::max_clients, session_host::next_client_id, session_host_client_t::participant_type, session_host_client_t::port, ringbuffer_create(), ringbuffer_destroy(), session_host_client_t::socket, session_host_client_t::transport, session_host::user_data, and session_host_client_t::video_active.

Referenced by add_client(), and add_webrtc_client().

◆ session_host_add_memory_participant()

uint32_t session_host_add_memory_participant ( session_host_t host)

Definition at line 996 of file host.c.

996 {
997 if (!host || !host->initialized) {
998 return 0;
999 }
1000
1001 mutex_lock(&host->clients_mutex);
1002
1003 // Check if we have room
1004 if (host->client_count >= host->max_clients) {
1005 mutex_unlock(&host->clients_mutex);
1006 SET_ERRNO(ERROR_SESSION_FULL, "Maximum clients reached");
1007 return 0;
1008 }
1009
1010 // Check if memory participant already exists (only one allowed)
1011 for (int i = 0; i < host->max_clients; i++) {
1012 if (host->clients[i].active && host->clients[i].participant_type == PARTICIPANT_TYPE_MEMORY) {
1013 mutex_unlock(&host->clients_mutex);
1014 SET_ERRNO(ERROR_INVALID_PARAM, "Memory participant already exists");
1015 return 0;
1016 }
1017 }
1018
1019 // Find empty slot
1020 for (int i = 0; i < host->max_clients; i++) {
1021 if (!host->clients[i].active) {
1022 host->clients[i].participant_type = PARTICIPANT_TYPE_MEMORY;
1023 host->clients[i].client_id = host->next_client_id++;
1024 host->clients[i].socket = INVALID_SOCKET_VALUE; // No socket for memory participant
1025 SAFE_STRNCPY(host->clients[i].ip_address, "memory", sizeof(host->clients[i].ip_address));
1026 host->clients[i].port = 0;
1027 host->clients[i].active = true;
1028 host->clients[i].video_active = false;
1029 host->clients[i].audio_active = false;
1030 host->clients[i].connected_at = (uint64_t)time(NULL);
1031 host->clients[i].transport = NULL;
1032
1033 // Allocate media buffers (same as network clients)
1034 host->clients[i].incoming_video = image_new(480, 270);
1035 host->clients[i].incoming_audio = ringbuffer_create(sizeof(float), 960 * 10);
1036
1037 if (!host->clients[i].incoming_video || !host->clients[i].incoming_audio) {
1038 if (host->clients[i].incoming_video) {
1040 host->clients[i].incoming_video = NULL;
1041 }
1042 if (host->clients[i].incoming_audio) {
1044 host->clients[i].incoming_audio = NULL;
1045 }
1046 host->clients[i].active = false;
1047 mutex_unlock(&host->clients_mutex);
1048 SET_ERRNO(ERROR_MEMORY, "Failed to allocate media buffers for memory participant");
1049 return 0;
1050 }
1051
1052 uint32_t participant_id = host->clients[i].client_id;
1053 host->client_count++;
1054
1055 mutex_unlock(&host->clients_mutex);
1056
1057 log_info("Added memory participant with ID %u", participant_id);
1058
1059 // Invoke callback
1060 if (host->callbacks.on_client_join) {
1061 host->callbacks.on_client_join(host, participant_id, host->user_data);
1062 }
1063
1064 return participant_id;
1065 }
1066 }
1067
1068 mutex_unlock(&host->clients_mutex);
1069 return 0;
1070}
uint8_t participant_id[16]

References session_host_client_t::active, session_host_client_t::audio_active, session_host::callbacks, session_host::client_count, session_host_client_t::client_id, session_host::clients, session_host::clients_mutex, session_host_client_t::connected_at, image_destroy(), image_new(), session_host_client_t::incoming_audio, session_host_client_t::incoming_video, session_host::initialized, session_host_client_t::ip_address, session_host::max_clients, session_host::next_client_id, participant_id, session_host_client_t::participant_type, session_host_client_t::port, ringbuffer_create(), ringbuffer_destroy(), session_host_client_t::socket, session_host_client_t::transport, session_host::user_data, and session_host_client_t::video_active.

◆ session_host_broadcast_frame()

asciichat_error_t session_host_broadcast_frame ( session_host_t host,
const char *  frame 
)

Definition at line 1258 of file host.c.

1258 {
1259 if (!host || !host->initialized || !frame) {
1260 return SET_ERRNO(ERROR_INVALID_PARAM, "session_host_broadcast_frame: invalid parameter");
1261 }
1262
1263 if (!host->running) {
1264 return SET_ERRNO(ERROR_INVALID_STATE, "session_host_broadcast_frame: not running");
1265 }
1266
1267 // Broadcast ASCII frame to all connected clients
1268 size_t frame_len = strlen(frame) + 1; // Include null terminator
1269 asciichat_error_t result = ASCIICHAT_OK;
1270
1271 mutex_lock(&host->clients_mutex);
1272 for (int i = 0; i < host->max_clients; i++) {
1273 if (host->clients[i].active && host->clients[i].socket != INVALID_SOCKET_VALUE) {
1274 asciichat_error_t send_result =
1275 packet_send(host->clients[i].socket, PACKET_TYPE_ASCII_FRAME, (const void *)frame, frame_len);
1276 if (send_result != ASCIICHAT_OK) {
1277 log_warn("Failed to send ASCII frame to client %u", host->clients[i].client_id);
1278 result = send_result; // Store error but continue broadcasting to other clients
1279 }
1280 }
1281 }
1282 mutex_unlock(&host->clients_mutex);
1283
1284 return result;
1285}
asciichat_error_t packet_send(socket_t sockfd, packet_type_t type, const void *data, size_t len)
Send a packet with proper header and CRC32.
Definition packet.c:288
bool running
Server is running.
Definition host.c:117

References session_host_client_t::active, session_host_client_t::client_id, session_host::clients, session_host::clients_mutex, session_host::initialized, session_host::max_clients, packet_send(), session_host::running, and session_host_client_t::socket.

◆ session_host_client_has_transport()

bool session_host_client_has_transport ( session_host_t host,
uint32_t  client_id 
)

Definition at line 1461 of file host.c.

1461 {
1462 if (!host || !host->initialized) {
1463 return false;
1464 }
1465
1466 mutex_lock(&host->clients_mutex);
1467
1468 // Find client with matching ID
1469 for (int i = 0; i < host->max_clients; i++) {
1470 if (host->clients[i].active && host->clients[i].client_id == client_id) {
1471 bool has_transport = host->clients[i].transport != NULL;
1472 mutex_unlock(&host->clients_mutex);
1473 return has_transport;
1474 }
1475 }
1476
1477 mutex_unlock(&host->clients_mutex);
1478 return false;
1479}

References session_host_client_t::active, session_host_client_t::client_id, session_host::clients, session_host::clients_mutex, session_host::initialized, session_host::max_clients, and session_host_client_t::transport.

◆ session_host_create()

session_host_t * session_host_create ( const session_host_config_t *  config)

Definition at line 166 of file host.c.

166 {
167 if (!config) {
168 SET_ERRNO(ERROR_INVALID_PARAM, "session_host_create: NULL config");
169 return NULL;
170 }
171
172 // Allocate host
173 session_host_t *host = SAFE_CALLOC(1, sizeof(session_host_t), session_host_t *);
174
175 // Copy configuration
176 host->port = config->port > 0 ? config->port : OPT_PORT_INT_DEFAULT;
177 host->max_clients = config->max_clients > 0 ? config->max_clients : SESSION_HOST_DEFAULT_MAX_CLIENTS;
178 host->encryption_enabled = config->encryption_enabled;
179
180 if (config->ipv4_address) {
181 SAFE_STRNCPY(host->ipv4_address, config->ipv4_address, sizeof(host->ipv4_address));
182 }
183
184 if (config->ipv6_address) {
185 SAFE_STRNCPY(host->ipv6_address, config->ipv6_address, sizeof(host->ipv6_address));
186 }
187
188 if (config->key_path) {
189 SAFE_STRNCPY(host->key_path, config->key_path, sizeof(host->key_path));
190 }
191
192 if (config->password) {
193 SAFE_STRNCPY(host->password, config->password, sizeof(host->password));
194 }
195
196 host->callbacks = config->callbacks;
197 host->user_data = config->user_data;
198
199 // Initialize sockets to invalid
200 host->socket_v4 = INVALID_SOCKET_VALUE;
201 host->socket_v6 = INVALID_SOCKET_VALUE;
202 host->running = false;
203
204 // Allocate client array
205 host->clients = SAFE_CALLOC((size_t)host->max_clients, sizeof(session_host_client_t), session_host_client_t *);
206
207 host->client_count = 0;
208 host->next_client_id = 1;
209
210 // Initialize mutex
211 if (mutex_init(&host->clients_mutex) != 0) {
212 SET_ERRNO(ERROR_THREAD, "Failed to initialize clients mutex");
213 SAFE_FREE(host->clients);
214 SAFE_FREE(host);
215 return NULL;
216 }
217
218 host->initialized = true;
219 return host;
220}
#define SESSION_HOST_DEFAULT_MAX_CLIENTS
Default maximum clients.
Definition host.c:43
Internal client record structure.
Definition host.c:52
Internal session host structure.
Definition host.c:82
char ipv6_address[64]
IPv6 bind address.
Definition host.c:90
char password[256]
Password.
Definition host.c:102
char ipv4_address[64]
IPv4 bind address.
Definition host.c:87
socket_t socket_v6
IPv6 listen socket.
Definition host.c:114
socket_t socket_v4
IPv4 listen socket.
Definition host.c:111
char key_path[512]
Key path.
Definition host.c:99
int port
Port to listen on.
Definition host.c:84
bool encryption_enabled
Encryption enabled.
Definition host.c:96
int mutex_init(mutex_t *mutex)
Definition threading.c:16

References session_host::callbacks, session_host::client_count, session_host::clients, session_host::clients_mutex, session_host::encryption_enabled, session_host::initialized, session_host::ipv4_address, session_host::ipv6_address, session_host::key_path, session_host::max_clients, mutex_init(), session_host::next_client_id, session_host::password, session_host::port, session_host::running, SESSION_HOST_DEFAULT_MAX_CLIENTS, session_host::socket_v4, session_host::socket_v6, and session_host::user_data.

Referenced by discovery_session_become_host(), discovery_session_process(), and server_main().

◆ session_host_destroy()

void session_host_destroy ( session_host_t host)

Definition at line 222 of file host.c.

222 {
223 if (!host) {
224 return;
225 }
226
227 // Stop if running
228 if (host->running) {
229 session_host_stop(host);
230 }
231
232 // Clean up audio resources
233 if (host->audio_ctx) {
235 host->audio_ctx = NULL;
236 }
237 if (host->opus_decoder) {
239 host->opus_decoder = NULL;
240 }
241
242 // Close sockets
243 if (host->socket_v4 != INVALID_SOCKET_VALUE) {
244 socket_close(host->socket_v4);
245 host->socket_v4 = INVALID_SOCKET_VALUE;
246 }
247 if (host->socket_v6 != INVALID_SOCKET_VALUE) {
248 socket_close(host->socket_v6);
249 host->socket_v6 = INVALID_SOCKET_VALUE;
250 }
251
252 // Free client array and per-client resources
253 if (host->clients) {
254 for (int i = 0; i < host->max_clients; i++) {
255 if (host->clients[i].incoming_video) {
257 host->clients[i].incoming_video = NULL;
258 }
259 if (host->clients[i].incoming_audio) {
261 host->clients[i].incoming_audio = NULL;
262 }
263 }
264 SAFE_FREE(host->clients);
265 }
266
267 // Destroy mutex
269
270 // Clear sensitive data
271 memset(host->password, 0, sizeof(host->password));
272
273 host->initialized = false;
274 SAFE_FREE(host);
275}
void session_host_stop(session_host_t *host)
Definition host.c:857
void session_audio_destroy(session_audio_ctx_t *ctx)
void opus_codec_destroy(opus_codec_t *codec)
Definition opus_codec.c:215
opus_codec_t * opus_decoder
Opus decoder for decoding incoming Opus audio.
Definition host.c:153
session_audio_ctx_t * audio_ctx
Audio context for mixing (host only)
Definition host.c:150
int mutex_destroy(mutex_t *mutex)
Definition threading.c:21

References session_host::audio_ctx, session_host::clients, session_host::clients_mutex, image_destroy(), session_host_client_t::incoming_audio, session_host_client_t::incoming_video, session_host::initialized, session_host::max_clients, mutex_destroy(), opus_codec_destroy(), session_host::opus_decoder, session_host::password, ringbuffer_destroy(), session_host::running, session_audio_destroy(), session_host_stop(), session_host::socket_v4, and session_host::socket_v6.

Referenced by discovery_session_destroy(), and server_main().

◆ session_host_find_client()

asciichat_error_t session_host_find_client ( session_host_t host,
uint32_t  client_id,
session_host_client_info_t *  info 
)

Definition at line 1204 of file host.c.

1204 {
1205 if (!host || !host->initialized || !info) {
1206 return SET_ERRNO(ERROR_INVALID_PARAM, "session_host_find_client: invalid parameter");
1207 }
1208
1209 mutex_lock(&host->clients_mutex);
1210
1211 for (int i = 0; i < host->max_clients; i++) {
1212 if (host->clients[i].active && host->clients[i].client_id == client_id) {
1213 info->client_id = host->clients[i].client_id;
1214 SAFE_STRNCPY(info->ip_address, host->clients[i].ip_address, sizeof(info->ip_address));
1215 info->port = host->clients[i].port;
1216 info->video_active = host->clients[i].video_active;
1217 info->audio_active = host->clients[i].audio_active;
1218 info->connected_at = host->clients[i].connected_at;
1219
1220 mutex_unlock(&host->clients_mutex);
1221 return ASCIICHAT_OK;
1222 }
1223 }
1224
1225 mutex_unlock(&host->clients_mutex);
1226 return SET_ERRNO(ERROR_NOT_FOUND, "Client not found: %u", client_id);
1227}

References session_host_client_t::active, session_host_client_t::audio_active, session_host_client_t::client_id, session_host::clients, session_host::clients_mutex, session_host_client_t::connected_at, session_host::initialized, session_host_client_t::ip_address, session_host::max_clients, session_host_client_t::port, and session_host_client_t::video_active.

◆ session_host_get_client_count()

int session_host_get_client_count ( session_host_t host)

Definition at line 1229 of file host.c.

1229 {
1230 if (!host || !host->initialized) {
1231 return 0;
1232 }
1233 return host->client_count;
1234}

References session_host::client_count, and session_host::initialized.

◆ session_host_get_client_ids()

int session_host_get_client_ids ( session_host_t host,
uint32_t *  ids,
int  max_ids 
)

Definition at line 1236 of file host.c.

1236 {
1237 if (!host || !host->initialized || !ids || max_ids <= 0) {
1238 return 0;
1239 }
1240
1241 mutex_lock(&host->clients_mutex);
1242
1243 int count = 0;
1244 for (int i = 0; i < host->max_clients && count < max_ids; i++) {
1245 if (host->clients[i].active) {
1246 ids[count++] = host->clients[i].client_id;
1247 }
1248 }
1249
1250 mutex_unlock(&host->clients_mutex);
1251 return count;
1252}

References session_host_client_t::active, session_host_client_t::client_id, session_host::clients, session_host::clients_mutex, session_host::initialized, and session_host::max_clients.

◆ session_host_get_client_transport()

acip_transport_t * session_host_get_client_transport ( session_host_t host,
uint32_t  client_id 
)

Definition at line 1441 of file host.c.

1441 {
1442 if (!host || !host->initialized) {
1443 return NULL;
1444 }
1445
1446 mutex_lock(&host->clients_mutex);
1447
1448 // Find client with matching ID
1449 for (int i = 0; i < host->max_clients; i++) {
1450 if (host->clients[i].active && host->clients[i].client_id == client_id) {
1451 acip_transport_t *transport = host->clients[i].transport;
1452 mutex_unlock(&host->clients_mutex);
1453 return transport;
1454 }
1455 }
1456
1457 mutex_unlock(&host->clients_mutex);
1458 return NULL;
1459}

References session_host_client_t::active, session_host_client_t::client_id, session_host::clients, session_host::clients_mutex, session_host::initialized, session_host::max_clients, and session_host_client_t::transport.

◆ session_host_inject_audio()

asciichat_error_t session_host_inject_audio ( session_host_t host,
uint32_t  participant_id,
const float *  samples,
size_t  count 
)

Definition at line 1117 of file host.c.

1118 {
1119 if (!host || !host->initialized || !samples || count == 0) {
1120 return SET_ERRNO(ERROR_INVALID_PARAM, "session_host_inject_audio: invalid parameters");
1121 }
1122
1123 mutex_lock(&host->clients_mutex);
1124
1125 // Find memory participant
1126 for (int i = 0; i < host->max_clients; i++) {
1127 if (host->clients[i].active && host->clients[i].client_id == participant_id &&
1128 host->clients[i].participant_type == PARTICIPANT_TYPE_MEMORY) {
1129
1130 if (!host->clients[i].incoming_audio) {
1131 mutex_unlock(&host->clients_mutex);
1132 return SET_ERRNO(ERROR_INVALID_STATE, "Memory participant has no audio buffer");
1133 }
1134
1135 // Write samples to ringbuffer (one at a time)
1136 size_t written = 0;
1137 for (size_t j = 0; j < count; j++) {
1138 if (ringbuffer_write(host->clients[i].incoming_audio, &samples[j])) {
1139 written++;
1140 } else {
1141 break; // Buffer full
1142 }
1143 }
1144
1145 if (written < count) {
1146 log_warn_every(NS_PER_MS_INT, "Audio ringbuffer full, dropped %zu samples", count - written);
1147 }
1148
1149 host->clients[i].audio_active = true;
1150
1151 mutex_unlock(&host->clients_mutex);
1152 return ASCIICHAT_OK;
1153 }
1154 }
1155
1156 mutex_unlock(&host->clients_mutex);
1157 return SET_ERRNO(ERROR_NOT_FOUND, "Memory participant not found");
1158}
bool ringbuffer_write(ringbuffer_t *rb, const void *data)
Definition ringbuffer.c:61

References session_host_client_t::active, session_host_client_t::audio_active, session_host_client_t::client_id, session_host::clients, session_host::clients_mutex, session_host_client_t::incoming_audio, session_host::initialized, session_host::max_clients, participant_id, session_host_client_t::participant_type, and ringbuffer_write().

◆ session_host_inject_frame()

asciichat_error_t session_host_inject_frame ( session_host_t host,
uint32_t  participant_id,
const image_t *  frame 
)

Definition at line 1072 of file host.c.

1072 {
1073 if (!host || !host->initialized || !frame) {
1074 return SET_ERRNO(ERROR_INVALID_PARAM, "session_host_inject_frame: invalid parameters");
1075 }
1076
1077 mutex_lock(&host->clients_mutex);
1078
1079 // Find memory participant
1080 for (int i = 0; i < host->max_clients; i++) {
1081 if (host->clients[i].active && host->clients[i].client_id == participant_id &&
1082 host->clients[i].participant_type == PARTICIPANT_TYPE_MEMORY) {
1083
1084 if (!host->clients[i].incoming_video) {
1085 mutex_unlock(&host->clients_mutex);
1086 return SET_ERRNO(ERROR_INVALID_STATE, "Memory participant has no video buffer");
1087 }
1088
1089 // Copy frame data into buffer
1090 image_t *dest = host->clients[i].incoming_video;
1091 if (dest->w != frame->w || dest->h != frame->h) {
1092 // Reallocate if size changed
1093 image_destroy(dest);
1094 dest = image_new(frame->w, frame->h);
1095 if (!dest) {
1096 host->clients[i].incoming_video = NULL;
1097 mutex_unlock(&host->clients_mutex);
1098 return SET_ERRNO(ERROR_MEMORY, "Failed to reallocate video buffer");
1099 }
1100 host->clients[i].incoming_video = dest;
1101 }
1102
1103 // Copy pixel data
1104 memcpy(dest->pixels, frame->pixels, frame->w * frame->h * sizeof(rgb_pixel_t));
1105
1106 host->clients[i].video_active = true;
1107
1108 mutex_unlock(&host->clients_mutex);
1109 return ASCIICHAT_OK;
1110 }
1111 }
1112
1113 mutex_unlock(&host->clients_mutex);
1114 return SET_ERRNO(ERROR_NOT_FOUND, "Memory participant not found");
1115}

References session_host_client_t::active, session_host_client_t::client_id, session_host::clients, session_host::clients_mutex, image_destroy(), image_new(), session_host_client_t::incoming_video, session_host::initialized, session_host::max_clients, participant_id, session_host_client_t::participant_type, and session_host_client_t::video_active.

◆ session_host_is_running()

bool session_host_is_running ( session_host_t host)

Definition at line 917 of file host.c.

917 {
918 if (!host || !host->initialized) {
919 return false;
920 }
921 return host->running;
922}

References session_host::initialized, and session_host::running.

◆ session_host_remove_client()

asciichat_error_t session_host_remove_client ( session_host_t host,
uint32_t  client_id 
)

Definition at line 1160 of file host.c.

1160 {
1161 if (!host || !host->initialized) {
1162 return SET_ERRNO(ERROR_INVALID_PARAM, "session_host_remove_client: invalid host");
1163 }
1164
1165 mutex_lock(&host->clients_mutex);
1166
1167 for (int i = 0; i < host->max_clients; i++) {
1168 if (host->clients[i].active && host->clients[i].client_id == client_id) {
1169 // Invoke callback before removing
1170 mutex_unlock(&host->clients_mutex);
1171 if (host->callbacks.on_client_leave) {
1172 host->callbacks.on_client_leave(host, client_id, host->user_data);
1173 }
1174 mutex_lock(&host->clients_mutex);
1175
1176 // Close socket
1177 if (host->clients[i].socket != INVALID_SOCKET_VALUE) {
1178 socket_close(host->clients[i].socket);
1179 host->clients[i].socket = INVALID_SOCKET_VALUE;
1180 }
1181
1182 // Clean up media buffers
1183 if (host->clients[i].incoming_video) {
1185 host->clients[i].incoming_video = NULL;
1186 }
1187 if (host->clients[i].incoming_audio) {
1189 host->clients[i].incoming_audio = NULL;
1190 }
1191
1192 host->clients[i].active = false;
1193 host->client_count--;
1194
1195 mutex_unlock(&host->clients_mutex);
1196 return ASCIICHAT_OK;
1197 }
1198 }
1199
1200 mutex_unlock(&host->clients_mutex);
1201 return SET_ERRNO(ERROR_NOT_FOUND, "Client not found: %u", client_id);
1202}

References session_host_client_t::active, session_host::callbacks, session_host::client_count, session_host_client_t::client_id, session_host::clients, session_host::clients_mutex, image_destroy(), session_host_client_t::incoming_audio, session_host_client_t::incoming_video, session_host::initialized, session_host::max_clients, ringbuffer_destroy(), session_host_client_t::socket, and session_host::user_data.

Referenced by remove_client().

◆ session_host_send_frame()

asciichat_error_t session_host_send_frame ( session_host_t host,
uint32_t  client_id,
const char *  frame 
)

Definition at line 1287 of file host.c.

1287 {
1288 if (!host || !host->initialized || !frame) {
1289 return SET_ERRNO(ERROR_INVALID_PARAM, "session_host_send_frame: invalid parameter");
1290 }
1291
1292 if (!host->running) {
1293 return SET_ERRNO(ERROR_INVALID_STATE, "session_host_send_frame: not running");
1294 }
1295
1296 // Send ASCII frame to specific client
1297 size_t frame_len = strlen(frame) + 1; // Include null terminator
1298
1299 mutex_lock(&host->clients_mutex);
1300 for (int i = 0; i < host->max_clients; i++) {
1301 if (host->clients[i].client_id == client_id && host->clients[i].active &&
1302 host->clients[i].socket != INVALID_SOCKET_VALUE) {
1303 asciichat_error_t result =
1304 packet_send(host->clients[i].socket, PACKET_TYPE_ASCII_FRAME, (const void *)frame, frame_len);
1305 mutex_unlock(&host->clients_mutex);
1306 return result;
1307 }
1308 }
1309 mutex_unlock(&host->clients_mutex);
1310
1311 return SET_ERRNO(ERROR_NOT_FOUND, "session_host_send_frame: client %u not found", client_id);
1312}

References session_host_client_t::active, session_host_client_t::client_id, session_host::clients, session_host::clients_mutex, session_host::initialized, session_host::max_clients, packet_send(), session_host::running, and session_host_client_t::socket.

◆ session_host_set_client_transport()

asciichat_error_t session_host_set_client_transport ( session_host_t host,
uint32_t  client_id,
acip_transport_t *  transport 
)

Definition at line 1409 of file host.c.

1410 {
1411 if (!host || !host->initialized) {
1412 return SET_ERRNO(ERROR_INVALID_PARAM, "Host is NULL or not initialized");
1413 }
1414
1415 mutex_lock(&host->clients_mutex);
1416
1417 // Find client with matching ID
1418 for (int i = 0; i < host->max_clients; i++) {
1419 if (host->clients[i].active && host->clients[i].client_id == client_id) {
1420 log_info("session_host_set_client_transport: Setting transport=%p for client %u (was=%p)", transport, client_id,
1421 host->clients[i].transport);
1422
1423 host->clients[i].transport = transport;
1424
1425 if (transport) {
1426 log_info("WebRTC transport now active for client %u", client_id);
1427 } else {
1428 log_info("WebRTC transport cleared for client %u, reverting to socket", client_id);
1429 }
1430
1431 mutex_unlock(&host->clients_mutex);
1432 return ASCIICHAT_OK;
1433 }
1434 }
1435
1436 mutex_unlock(&host->clients_mutex);
1437 log_warn("Client %u not found", client_id);
1438 return SET_ERRNO(ERROR_NOT_FOUND, "Client not found");
1439}

References session_host_client_t::active, session_host_client_t::client_id, session_host::clients, session_host::clients_mutex, session_host::initialized, session_host::max_clients, and session_host_client_t::transport.

◆ session_host_start()

asciichat_error_t session_host_start ( session_host_t host)

Definition at line 797 of file host.c.

797 {
798 if (!host || !host->initialized) {
799 return SET_ERRNO(ERROR_INVALID_PARAM, "session_host_start: invalid host");
800 }
801
802 if (host->running) {
803 return ASCIICHAT_OK; // Already running
804 }
805
806 // Create listen socket(s)
807 // For simplicity, we bind to IPv4 if specified, otherwise default to 0.0.0.0
808 const char *bind_address = host->ipv4_address[0] ? host->ipv4_address : "0.0.0.0";
809
810 host->socket_v4 = create_listen_socket(bind_address, host->port);
811 if (host->socket_v4 == INVALID_SOCKET_VALUE) {
812 log_error("Failed to create IPv4 listen socket");
813 if (host->callbacks.on_error) {
814 host->callbacks.on_error(host, ERROR_NETWORK_BIND, "Failed to create listen socket", host->user_data);
815 }
816 return GET_ERRNO();
817 }
818
819 host->running = true;
820 log_info("Session host listening on %s:%d", bind_address, host->port);
821
822 // Spawn accept loop thread
823 host->accept_thread_running = true;
824 if (asciichat_thread_create(&host->accept_thread, accept_loop_thread, host) != 0) {
825 log_error("Failed to spawn accept loop thread");
826 host->accept_thread_running = false;
827 if (host->callbacks.on_error) {
828 host->callbacks.on_error(host, ERROR_THREAD, "Failed to spawn accept loop thread", host->user_data);
829 }
830 socket_close(host->socket_v4);
831 host->socket_v4 = INVALID_SOCKET_VALUE;
832 host->running = false;
833 return SET_ERRNO(ERROR_THREAD, "Failed to spawn accept loop thread");
834 }
835
836 // Spawn receive loop thread
837 host->receive_thread_running = true;
838 if (asciichat_thread_create(&host->receive_thread, receive_loop_thread, host) != 0) {
839 log_error("Failed to spawn receive loop thread");
840 host->receive_thread_running = false;
841 host->accept_thread_running = false;
843 if (host->callbacks.on_error) {
844 host->callbacks.on_error(host, ERROR_THREAD, "Failed to spawn receive loop thread", host->user_data);
845 }
846 socket_close(host->socket_v4);
847 host->socket_v4 = INVALID_SOCKET_VALUE;
848 host->running = false;
849 return SET_ERRNO(ERROR_THREAD, "Failed to spawn receive loop thread");
850 }
851
852 // Spawn render thread (optional - can be started later)
853 // This thread handles video mixing and audio distribution
854 return ASCIICHAT_OK;
855}
asciichat_thread_t receive_thread
Receive thread handle.
Definition host.c:138
bool receive_thread_running
Receive thread is running.
Definition host.c:141
asciichat_thread_t accept_thread
Accept thread handle.
Definition host.c:132
bool accept_thread_running
Accept thread is running.
Definition host.c:135
int asciichat_thread_create(asciichat_thread_t *thread, void *(*start_routine)(void *), void *arg)
Definition threading.c:42
int asciichat_thread_join(asciichat_thread_t *thread, void **retval)
Definition threading.c:46

References session_host::accept_thread, session_host::accept_thread_running, asciichat_thread_create(), asciichat_thread_join(), session_host::callbacks, session_host::initialized, session_host::ipv4_address, session_host::port, session_host::receive_thread, session_host::receive_thread_running, session_host::running, session_host::socket_v4, and session_host::user_data.

Referenced by discovery_session_become_host(), and discovery_session_process().

◆ session_host_start_render()

asciichat_error_t session_host_start_render ( session_host_t host)

Definition at line 1318 of file host.c.

1318 {
1319 if (!host || !host->initialized) {
1320 return SET_ERRNO(ERROR_INVALID_PARAM, "session_host_start_render: invalid host");
1321 }
1322
1323 if (!host->running) {
1324 return SET_ERRNO(ERROR_INVALID_STATE, "session_host_start_render: not running");
1325 }
1326
1327 if (host->render_thread_running) {
1328 return ASCIICHAT_OK; // Already running
1329 }
1330
1331 // Create audio context for mixing (host mode = true)
1332 if (!host->audio_ctx) {
1333 host->audio_ctx = session_audio_create(true);
1334 if (!host->audio_ctx) {
1335 return SET_ERRNO(ERROR_INVALID_STATE, "Failed to create audio context");
1336 }
1337 }
1338
1339 // Create Opus decoder for decoding incoming Opus audio (48kHz)
1340 if (!host->opus_decoder) {
1342 if (!host->opus_decoder) {
1344 host->audio_ctx = NULL;
1345 return SET_ERRNO(ERROR_INVALID_STATE, "Failed to create Opus decoder");
1346 }
1347 }
1348
1349 // Create Opus encoder for encoding mixed audio for broadcast (48kHz, VOIP mode, 24kbps)
1350 if (!host->opus_encoder) {
1351 host->opus_encoder = opus_codec_create_encoder(OPUS_APPLICATION_VOIP, 48000, 24000);
1352 if (!host->opus_encoder) {
1354 host->opus_decoder = NULL;
1356 host->audio_ctx = NULL;
1357 return SET_ERRNO(ERROR_INVALID_STATE, "Failed to create Opus encoder");
1358 }
1359 }
1360
1361 // Spawn render thread
1362 host->render_thread_running = true;
1363 if (asciichat_thread_create(&host->render_thread, host_render_thread, host) != 0) {
1364 log_error("Failed to spawn render thread");
1365 host->render_thread_running = false;
1366 return SET_ERRNO(ERROR_THREAD, "Failed to spawn render thread");
1367 }
1368
1369 log_info("Host render thread started");
1370 return ASCIICHAT_OK;
1371}
session_audio_ctx_t * session_audio_create(bool is_host)
opus_codec_t * opus_codec_create_decoder(int sample_rate)
Definition opus_codec.c:62
opus_codec_t * opus_codec_create_encoder(opus_application_t application, int sample_rate, int bitrate)
Definition opus_codec.c:18
opus_codec_t * opus_encoder
Opus encoder for encoding mixed audio for broadcast.
Definition host.c:156
asciichat_thread_t render_thread
Render thread handle (for video mixing and audio distribution)
Definition host.c:144
bool render_thread_running
Render thread is running.
Definition host.c:147

References asciichat_thread_create(), session_host::audio_ctx, session_host::initialized, opus_codec_create_decoder(), opus_codec_create_encoder(), opus_codec_destroy(), session_host::opus_decoder, session_host::opus_encoder, session_host::render_thread, session_host::render_thread_running, session_host::running, session_audio_create(), and session_audio_destroy().

◆ session_host_stop()

void session_host_stop ( session_host_t host)

Definition at line 857 of file host.c.

857 {
858 if (!host || !host->initialized || !host->running) {
859 return;
860 }
861
862 // Stop render thread if running
863 if (host->render_thread_running) {
864 host->render_thread_running = false;
866 log_info("Render thread joined");
867 }
868
869 // Stop receive loop thread (reads from client sockets)
870 if (host->receive_thread_running) {
871 host->receive_thread_running = false;
873 log_info("Receive loop thread joined");
874 }
875
876 // Stop accept loop thread (before closing listen socket)
877 if (host->accept_thread_running) {
878 host->accept_thread_running = false;
880 log_info("Accept loop thread joined");
881 }
882
883 // Disconnect all clients
884 mutex_lock(&host->clients_mutex);
885 for (int i = 0; i < host->max_clients; i++) {
886 if (host->clients[i].active) {
887 // Invoke callback
888 if (host->callbacks.on_client_leave) {
889 host->callbacks.on_client_leave(host, host->clients[i].client_id, host->user_data);
890 }
891
892 // Close socket
893 if (host->clients[i].socket != INVALID_SOCKET_VALUE) {
894 socket_close(host->clients[i].socket);
895 host->clients[i].socket = INVALID_SOCKET_VALUE;
896 }
897
898 host->clients[i].active = false;
899 }
900 }
901 host->client_count = 0;
902 mutex_unlock(&host->clients_mutex);
903
904 // Close listen sockets
905 if (host->socket_v4 != INVALID_SOCKET_VALUE) {
906 socket_close(host->socket_v4);
907 host->socket_v4 = INVALID_SOCKET_VALUE;
908 }
909 if (host->socket_v6 != INVALID_SOCKET_VALUE) {
910 socket_close(host->socket_v6);
911 host->socket_v6 = INVALID_SOCKET_VALUE;
912 }
913
914 host->running = false;
915}

References session_host::accept_thread, session_host::accept_thread_running, session_host_client_t::active, asciichat_thread_join(), session_host::callbacks, session_host::client_count, session_host_client_t::client_id, session_host::clients, session_host::clients_mutex, session_host::initialized, session_host::max_clients, session_host::receive_thread, session_host::receive_thread_running, session_host::render_thread, session_host::render_thread_running, session_host::running, session_host_client_t::socket, session_host::socket_v4, session_host::socket_v6, and session_host::user_data.

Referenced by session_host_destroy().

◆ session_host_stop_render()

void session_host_stop_render ( session_host_t host)

Definition at line 1373 of file host.c.

1373 {
1374 if (!host || !host->initialized) {
1375 return;
1376 }
1377
1378 if (!host->render_thread_running) {
1379 return;
1380 }
1381
1382 // Signal thread to stop
1383 host->render_thread_running = false;
1384
1385 // Wait for thread to complete
1387
1388 // Clean up audio resources
1389 if (host->audio_ctx) {
1391 host->audio_ctx = NULL;
1392 }
1393 if (host->opus_decoder) {
1395 host->opus_decoder = NULL;
1396 }
1397 if (host->opus_encoder) {
1399 host->opus_encoder = NULL;
1400 }
1401
1402 log_info("Host render thread stopped");
1403}

References asciichat_thread_join(), session_host::audio_ctx, session_host::initialized, opus_codec_destroy(), session_host::opus_decoder, session_host::opus_encoder, session_host::render_thread, session_host::render_thread_running, and session_audio_destroy().