ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
acds_client.c
Go to the documentation of this file.
1
16#include "buffer_pool.h"
17#include "common.h"
18#include "crypto/crypto.h"
19#include "log/logging.h"
20#include "network/packet.h"
21#include "platform/socket.h"
22#include "util/endian.h"
23
24#include <string.h>
25#include <time.h>
26
27#ifdef _WIN32
28#include <winsock2.h>
29#else
30#include <sys/socket.h>
31#include <sys/types.h>
32#endif
33
34// ============================================================================
35// Helper Functions
36// ============================================================================
37
39 if (!config) {
40 return;
41 }
42
43 memset(config, 0, sizeof(*config));
44 SAFE_STRNCPY(config->server_address, "127.0.0.1", sizeof(config->server_address));
46 config->timeout_ms = 5000;
47}
48
49// ============================================================================
50// Connection Management
51// ============================================================================
52
54 if (!client || !config) {
55 return SET_ERRNO(ERROR_INVALID_PARAM, "client or config is NULL");
56 }
57
58 memset(client, 0, sizeof(*client));
59 memcpy(&client->config, config, sizeof(acds_client_config_t));
61 client->connected = false;
62
63 // Create TCP socket
64 client->socket = socket(AF_INET, SOCK_STREAM, 0);
65 if (client->socket == INVALID_SOCKET_VALUE) {
66 return SET_ERRNO(ERROR_NETWORK, "Failed to create socket: %s", socket_get_error_string());
67 }
68
69 // Set socket timeouts using platform abstraction
70 if (socket_set_timeout(client->socket, config->timeout_ms) != 0) {
71 socket_close(client->socket);
73 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to set socket timeouts");
74 }
75
76 // Connect to server
77 struct sockaddr_in server_addr;
78 memset(&server_addr, 0, sizeof(server_addr));
79 server_addr.sin_family = AF_INET;
80 server_addr.sin_port = htons(config->server_port);
81
82 if (inet_pton(AF_INET, config->server_address, &server_addr.sin_addr) <= 0) {
83 socket_close(client->socket);
85 return SET_ERRNO(ERROR_NETWORK, "Invalid server address: %s", config->server_address);
86 }
87
88 if (connect(client->socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
89 socket_close(client->socket);
91 return SET_ERRNO(ERROR_NETWORK, "Failed to connect to %s:%d: %s", config->server_address, config->server_port,
93 }
94
95 client->connected = true;
96 log_info("Connected to ACDS server at %s:%d", config->server_address, config->server_port);
97
98 return ASCIICHAT_OK;
99}
100
102 if (!client) {
103 return;
104 }
105
106 if (client->socket != INVALID_SOCKET_VALUE) {
107 socket_close(client->socket);
109 }
110
111 client->connected = false;
112 log_debug("Disconnected from ACDS server");
113}
114
115// ============================================================================
116// Session Management
117// ============================================================================
118
121 if (!client || !params || !result) {
122 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
123 }
124
125 if (!client->connected) {
126 return SET_ERRNO(ERROR_NETWORK, "Not connected to ACDS server");
127 }
128
129 // Build SESSION_CREATE payload
131 memset(&req, 0, sizeof(req));
132
133 memcpy(req.identity_pubkey, params->identity_pubkey, 32);
134
135 // Sign the request: type || timestamp || capabilities
136 uint64_t timestamp = (uint64_t)time(NULL) * 1000; // Unix ms
137 req.timestamp = timestamp;
138 req.capabilities = params->capabilities;
139 req.max_participants = params->max_participants;
140
141 // Generate Ed25519 signature for identity verification
142 asciichat_error_t sign_result = acds_sign_session_create(params->identity_seckey, timestamp, params->capabilities,
143 params->max_participants, req.signature);
144 if (sign_result != ASCIICHAT_OK) {
145 return SET_ERRNO(ERROR_CRYPTO, "Failed to sign SESSION_CREATE request");
146 }
147
148 req.has_password = params->has_password ? 1 : 0;
149 if (params->has_password) {
150 // Hash password with Argon2id using libsodium
151 // crypto_pwhash_str produces a null-terminated ASCII string
152 if (crypto_pwhash_str((char *)req.password_hash, params->password, strlen(params->password),
153 crypto_pwhash_OPSLIMIT_INTERACTIVE, crypto_pwhash_MEMLIMIT_INTERACTIVE) != 0) {
154 return SET_ERRNO(ERROR_CRYPTO, "Failed to hash password (out of memory)");
155 }
156 } else {
157 memset(req.password_hash, 0, sizeof(req.password_hash));
158 }
159
160 req.reserved_string_len = 0; // Auto-generate
161 if (params->reserved_string && params->reserved_string[0] != '\0') {
162 req.reserved_string_len = strlen(params->reserved_string);
163 // Variable part would follow here (not implemented yet)
164 }
165
166 // Server connection information
167 SAFE_STRNCPY(req.server_address, params->server_address, sizeof(req.server_address));
168 req.server_port = params->server_port;
169
170 // IP disclosure policy
171 // Auto-detection: If password is set, IP will be revealed after verification
172 // If no password, require explicit opt-in via acds_expose_ip
173 req.expose_ip_publicly = params->acds_expose_ip ? 1 : 0;
174
175 // Session type (Direct TCP or WebRTC)
176 req.session_type = params->session_type;
177
178 // Send SESSION_CREATE packet
179 asciichat_error_t send_result = send_packet(client->socket, PACKET_TYPE_ACIP_SESSION_CREATE, &req, sizeof(req));
180 if (send_result != ASCIICHAT_OK) {
181 return send_result;
182 }
183
184 log_debug("Sent SESSION_CREATE request");
185
186 // Receive SESSION_CREATED response
187 packet_type_t resp_type;
188 void *resp_payload = NULL;
189 size_t resp_size = 0;
190
191 int recv_result = receive_packet(client->socket, &resp_type, &resp_payload, &resp_size);
192 if (recv_result < 0) {
193 return SET_ERRNO(ERROR_NETWORK, "Failed to receive SESSION_CREATED response");
194 }
195
196 // Check response type
197 if (resp_type != PACKET_TYPE_ACIP_SESSION_CREATED) {
198 if (resp_type == PACKET_TYPE_ERROR_MESSAGE || resp_type == PACKET_TYPE_ACIP_ERROR) {
199 log_error("Session creation failed: server returned error packet");
200 buffer_pool_free(NULL, resp_payload, resp_size);
201 return SET_ERRNO(ERROR_NETWORK, "Session creation failed");
202 }
203 buffer_pool_free(NULL, resp_payload, resp_size);
204 return SET_ERRNO(ERROR_NETWORK, "Unexpected response type: 0x%02X", resp_type);
205 }
206
207 // Parse SESSION_CREATED response
208 if (resp_size < sizeof(acip_session_created_t)) {
209 buffer_pool_free(NULL, resp_payload, resp_size);
210 return SET_ERRNO(ERROR_NETWORK, "SESSION_CREATED response too small: %zu bytes", resp_size);
211 }
212
213 acip_session_created_t *resp = (acip_session_created_t *)resp_payload;
214
215 // Copy session string (null-terminate)
216 size_t string_len = resp->session_string_len < sizeof(result->session_string) - 1
217 ? resp->session_string_len
218 : sizeof(result->session_string) - 1;
219 memcpy(result->session_string, resp->session_string, string_len);
220 result->session_string[string_len] = '\0';
221
222 memcpy(result->session_id, resp->session_id, 16);
223 result->expires_at = resp->expires_at;
224
225 log_info("Session created: %s (expires at %llu)", result->session_string, (unsigned long long)result->expires_at);
226
227 buffer_pool_free(NULL, resp_payload, resp_size);
228 return ASCIICHAT_OK;
229}
230
231asciichat_error_t acds_session_lookup(acds_client_t *client, const char *session_string,
233 if (!client || !session_string || !result) {
234 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
235 }
236
237 if (!client->connected) {
238 return SET_ERRNO(ERROR_NETWORK, "Not connected to ACDS server");
239 }
240
241 // Build SESSION_LOOKUP payload
243 memset(&req, 0, sizeof(req));
244
245 req.session_string_len = strlen(session_string);
246 if (req.session_string_len >= sizeof(req.session_string)) {
247 return SET_ERRNO(ERROR_INVALID_PARAM, "Session string too long");
248 }
249
250 memcpy(req.session_string, session_string, req.session_string_len);
251
252 // Send SESSION_LOOKUP packet
253 asciichat_error_t send_result = send_packet(client->socket, PACKET_TYPE_ACIP_SESSION_LOOKUP, &req, sizeof(req));
254 if (send_result != ASCIICHAT_OK) {
255 return send_result;
256 }
257
258 log_debug("Sent SESSION_LOOKUP request for '%s'", session_string);
259
260 // Receive SESSION_INFO response
261 packet_type_t resp_type;
262 void *resp_payload = NULL;
263 size_t resp_size = 0;
264
265 int recv_result = receive_packet(client->socket, &resp_type, &resp_payload, &resp_size);
266 if (recv_result < 0) {
267 return SET_ERRNO(ERROR_NETWORK, "Failed to receive SESSION_INFO response");
268 }
269
270 if (resp_type != PACKET_TYPE_ACIP_SESSION_INFO) {
271 buffer_pool_free(NULL, resp_payload, resp_size);
272 return SET_ERRNO(ERROR_NETWORK, "Unexpected response type: 0x%02X", resp_type);
273 }
274
275 if (resp_size < sizeof(acip_session_info_t)) {
276 buffer_pool_free(NULL, resp_payload, resp_size);
277 return SET_ERRNO(ERROR_NETWORK, "SESSION_INFO response too small");
278 }
279
280 acip_session_info_t *resp = (acip_session_info_t *)resp_payload;
281
282 // Copy result
283 result->found = resp->found != 0;
284 if (result->found) {
285 memcpy(result->session_id, resp->session_id, 16);
286 memcpy(result->host_pubkey, resp->host_pubkey, 32);
287 result->capabilities = resp->capabilities;
288 result->max_participants = resp->max_participants;
289 result->current_participants = resp->current_participants;
290 result->has_password = resp->has_password != 0;
291 result->created_at = resp->created_at;
292 result->expires_at = resp->expires_at;
293 result->require_server_verify = resp->require_server_verify != 0;
294 result->require_client_verify = resp->require_client_verify != 0;
295
296 log_info("Session found: %s (%d/%d participants, password=%s, policies: server_verify=%d client_verify=%d)",
297 session_string, result->current_participants, result->max_participants,
298 result->has_password ? "required" : "not required", result->require_server_verify,
299 result->require_client_verify);
300 } else {
301 log_info("Session not found: %s", session_string);
302 }
303
304 buffer_pool_free(NULL, resp_payload, resp_size);
305 return ASCIICHAT_OK;
306}
307
310 if (!client || !params || !result) {
311 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
312 }
313
314 if (!client->connected) {
315 return SET_ERRNO(ERROR_NETWORK, "Not connected to ACDS server");
316 }
317
318 // Build SESSION_JOIN payload
320 memset(&req, 0, sizeof(req));
321
322 req.session_string_len = strlen(params->session_string);
323 if (req.session_string_len >= sizeof(req.session_string)) {
324 return SET_ERRNO(ERROR_INVALID_PARAM, "Session string too long");
325 }
326
327 memcpy(req.session_string, params->session_string, req.session_string_len);
328 memcpy(req.identity_pubkey, params->identity_pubkey, 32);
329
330 // Sign the request: type || timestamp || session_string
331 uint64_t timestamp = (uint64_t)time(NULL) * 1000;
332 req.timestamp = timestamp;
333
334 // Generate Ed25519 signature for identity verification
335 asciichat_error_t sign_result =
336 acds_sign_session_join(params->identity_seckey, timestamp, params->session_string, req.signature);
337 if (sign_result != ASCIICHAT_OK) {
338 return SET_ERRNO(ERROR_CRYPTO, "Failed to sign SESSION_JOIN request");
339 }
340
341 req.has_password = params->has_password ? 1 : 0;
342 if (params->has_password) {
343 SAFE_STRNCPY(req.password, params->password, sizeof(req.password));
344 }
345
346 // Send SESSION_JOIN packet
347 asciichat_error_t send_result = send_packet(client->socket, PACKET_TYPE_ACIP_SESSION_JOIN, &req, sizeof(req));
348 if (send_result != ASCIICHAT_OK) {
349 return send_result;
350 }
351
352 log_debug("Sent SESSION_JOIN request for '%s'", params->session_string);
353
354 // Receive SESSION_JOINED response
355 packet_type_t resp_type;
356 void *resp_payload = NULL;
357 size_t resp_size = 0;
358
359 int recv_result = receive_packet(client->socket, &resp_type, &resp_payload, &resp_size);
360 if (recv_result < 0) {
361 return SET_ERRNO(ERROR_NETWORK, "Failed to receive SESSION_JOINED response");
362 }
363
364 if (resp_type != PACKET_TYPE_ACIP_SESSION_JOINED) {
365 buffer_pool_free(NULL, resp_payload, resp_size);
366 return SET_ERRNO(ERROR_NETWORK, "Unexpected response type: 0x%02X", resp_type);
367 }
368
369 if (resp_size < sizeof(acip_session_joined_t)) {
370 buffer_pool_free(NULL, resp_payload, resp_size);
371 return SET_ERRNO(ERROR_NETWORK, "SESSION_JOINED response too small");
372 }
373
374 acip_session_joined_t *resp = (acip_session_joined_t *)resp_payload;
375
376 // Copy result
377 result->success = resp->success != 0;
378 if (result->success) {
379 memcpy(result->participant_id, resp->participant_id, 16);
380 memcpy(result->session_id, resp->session_id, 16);
381 // Server connection information (ONLY revealed after successful authentication)
382 result->session_type = resp->session_type;
383 SAFE_STRNCPY(result->server_address, resp->server_address, sizeof(result->server_address));
384 result->server_port = resp->server_port;
385 log_info("Joined session successfully (participant ID: %02x%02x..., server=%s:%d, type=%s)",
386 result->participant_id[0], result->participant_id[1], result->server_address, result->server_port,
387 result->session_type == SESSION_TYPE_WEBRTC ? "WebRTC" : "DirectTCP");
388 } else {
389 result->error_code = resp->error_code;
390 size_t msg_len = strnlen(resp->error_message, sizeof(resp->error_message));
391 if (msg_len >= sizeof(result->error_message)) {
392 msg_len = sizeof(result->error_message) - 1;
393 }
394 memcpy(result->error_message, resp->error_message, msg_len);
395 result->error_message[msg_len] = '\0';
396 log_warn("Failed to join session: %s (code %d)", result->error_message, result->error_code);
397 }
398
399 buffer_pool_free(NULL, resp_payload, resp_size);
400 return ASCIICHAT_OK;
401}
402
403// ============================================================================
404// Cryptographic Signature Helpers
405// ============================================================================
406
407asciichat_error_t acds_sign_session_create(const uint8_t identity_seckey[64], uint64_t timestamp, uint8_t capabilities,
408 uint8_t max_participants, uint8_t signature_out[64]) {
409 if (!identity_seckey || !signature_out) {
410 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL parameter to acds_sign_session_create");
411 }
412
413 // Build message: type (1 byte) || timestamp (8 bytes) || capabilities (1 byte) || max_participants (1 byte)
414 uint8_t message[11];
416
417 // Convert timestamp to big-endian (network byte order)
418 message[1] = (uint8_t)(timestamp >> 56);
419 message[2] = (uint8_t)(timestamp >> 48);
420 message[3] = (uint8_t)(timestamp >> 40);
421 message[4] = (uint8_t)(timestamp >> 32);
422 message[5] = (uint8_t)(timestamp >> 24);
423 message[6] = (uint8_t)(timestamp >> 16);
424 message[7] = (uint8_t)(timestamp >> 8);
425 message[8] = (uint8_t)(timestamp);
426
427 message[9] = capabilities;
428 message[10] = max_participants;
429
430 // Sign using Ed25519
431 if (crypto_sign_detached(signature_out, NULL, message, sizeof(message), identity_seckey) != 0) {
432 return SET_ERRNO(ERROR_CRYPTO, "Ed25519 signature generation failed");
433 }
434
435 log_debug("Generated SESSION_CREATE signature (timestamp=%llu, caps=%u, max=%u)", (unsigned long long)timestamp,
436 capabilities, max_participants);
437
438 return ASCIICHAT_OK;
439}
440
442 uint8_t capabilities, uint8_t max_participants,
443 const uint8_t signature[64]) {
444 if (!identity_pubkey || !signature) {
445 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL parameter to acds_verify_session_create");
446 }
447
448 // Build message: type (1 byte) || timestamp (8 bytes) || capabilities (1 byte) || max_participants (1 byte)
449 uint8_t message[11];
451
452 // Convert timestamp to big-endian (network byte order)
453 message[1] = (uint8_t)(timestamp >> 56);
454 message[2] = (uint8_t)(timestamp >> 48);
455 message[3] = (uint8_t)(timestamp >> 40);
456 message[4] = (uint8_t)(timestamp >> 32);
457 message[5] = (uint8_t)(timestamp >> 24);
458 message[6] = (uint8_t)(timestamp >> 16);
459 message[7] = (uint8_t)(timestamp >> 8);
460 message[8] = (uint8_t)(timestamp);
461
462 message[9] = capabilities;
463 message[10] = max_participants;
464
465 // Verify using Ed25519
466 if (crypto_sign_verify_detached(signature, message, sizeof(message), identity_pubkey) != 0) {
467 log_warn("SESSION_CREATE signature verification failed");
468 return SET_ERRNO(ERROR_CRYPTO_VERIFICATION, "Invalid SESSION_CREATE signature");
469 }
470
471 log_debug("SESSION_CREATE signature verified successfully");
472 return ASCIICHAT_OK;
473}
474
475asciichat_error_t acds_sign_session_join(const uint8_t identity_seckey[64], uint64_t timestamp,
476 const char *session_string, uint8_t signature_out[64]) {
477 if (!identity_seckey || !session_string || !signature_out) {
478 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL parameter to acds_sign_session_join");
479 }
480
481 size_t session_len = strlen(session_string);
482 if (session_len > 48) {
483 return SET_ERRNO(ERROR_INVALID_PARAM, "Session string too long (max 48 chars)");
484 }
485
486 // Build message: type (1 byte) || timestamp (8 bytes) || session_string (variable length)
487 uint8_t message[1 + 8 + 48];
489
490 // Convert timestamp to big-endian (network byte order)
491 message[1] = (uint8_t)(timestamp >> 56);
492 message[2] = (uint8_t)(timestamp >> 48);
493 message[3] = (uint8_t)(timestamp >> 40);
494 message[4] = (uint8_t)(timestamp >> 32);
495 message[5] = (uint8_t)(timestamp >> 24);
496 message[6] = (uint8_t)(timestamp >> 16);
497 message[7] = (uint8_t)(timestamp >> 8);
498 message[8] = (uint8_t)(timestamp);
499
500 // Copy session string (copy exactly session_len bytes, no null terminator in signature)
501 memcpy(&message[9], session_string, session_len);
502
503 size_t message_len = 9 + session_len;
504
505 // Sign using Ed25519
506 if (crypto_sign_detached(signature_out, NULL, message, message_len, identity_seckey) != 0) {
507 return SET_ERRNO(ERROR_CRYPTO, "Ed25519 signature generation failed");
508 }
509
510 log_debug("Generated SESSION_JOIN signature (timestamp=%llu, session='%s')", (unsigned long long)timestamp,
511 session_string);
512
513 return ASCIICHAT_OK;
514}
515
516asciichat_error_t acds_verify_session_join(const uint8_t identity_pubkey[32], uint64_t timestamp,
517 const char *session_string, const uint8_t signature[64]) {
518 if (!identity_pubkey || !session_string || !signature) {
519 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL parameter to acds_verify_session_join");
520 }
521
522 size_t session_len = strlen(session_string);
523 if (session_len > 48) {
524 return SET_ERRNO(ERROR_INVALID_PARAM, "Session string too long (max 48 chars)");
525 }
526
527 // Build message: type (1 byte) || timestamp (8 bytes) || session_string (variable length)
528 uint8_t message[1 + 8 + 48];
530
531 // Convert timestamp to big-endian (network byte order)
532 message[1] = (uint8_t)(timestamp >> 56);
533 message[2] = (uint8_t)(timestamp >> 48);
534 message[3] = (uint8_t)(timestamp >> 40);
535 message[4] = (uint8_t)(timestamp >> 32);
536 message[5] = (uint8_t)(timestamp >> 24);
537 message[6] = (uint8_t)(timestamp >> 16);
538 message[7] = (uint8_t)(timestamp >> 8);
539 message[8] = (uint8_t)(timestamp);
540
541 // Copy session string (copy exactly session_len bytes, no null terminator in signature)
542 memcpy(&message[9], session_string, session_len);
543
544 size_t message_len = 9 + session_len;
545
546 // Verify using Ed25519
547 if (crypto_sign_verify_detached(signature, message, message_len, identity_pubkey) != 0) {
548 log_warn("SESSION_JOIN signature verification failed");
549 return SET_ERRNO(ERROR_CRYPTO_VERIFICATION, "Invalid SESSION_JOIN signature");
550 }
551
552 log_debug("SESSION_JOIN signature verified successfully");
553 return ASCIICHAT_OK;
554}
555
556bool acds_validate_timestamp(uint64_t timestamp_ms, uint32_t window_seconds) {
557 // Get current time in milliseconds
558 struct timespec ts;
559 if (clock_gettime(CLOCK_REALTIME, &ts) != 0) {
560 log_error("clock_gettime failed");
561 return false;
562 }
563
564 uint64_t now_ms = (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
565 uint64_t window_ms = (uint64_t)window_seconds * 1000;
566
567 // Check if timestamp is too far in the future (allow 60 second clock skew)
568 if (timestamp_ms > now_ms + 60000) {
569 // Cast to signed before subtraction to avoid unsigned underflow
570 int64_t skew = (int64_t)timestamp_ms - (int64_t)now_ms;
571 log_warn("Timestamp is in the future: %llu > %llu (skew: %lld ms)", (unsigned long long)timestamp_ms,
572 (unsigned long long)now_ms, (long long)skew);
573 return false;
574 }
575
576 // Check if timestamp is too old
577 // To avoid unsigned underflow, check if now_ms is large enough before subtracting
578 uint64_t min_valid_timestamp = (now_ms >= window_ms) ? (now_ms - window_ms) : 0;
579 if (timestamp_ms < min_valid_timestamp) {
580 // Cast to signed before subtraction to avoid unsigned underflow
581 int64_t age = (int64_t)now_ms - (int64_t)timestamp_ms;
582 log_warn("Timestamp is too old: %llu < %llu (age: %lld ms, max: %u seconds)", (unsigned long long)timestamp_ms,
583 (unsigned long long)min_valid_timestamp, (long long)age, window_seconds);
584 return false;
585 }
586
587 // Cast to signed before subtraction to avoid unsigned underflow
588 int64_t age = (int64_t)now_ms - (int64_t)timestamp_ms;
589 log_debug("Timestamp validation passed (age: %lld ms, window: %u seconds)", (long long)age, window_seconds);
590 return true;
591}
asciichat_error_t acds_verify_session_create(const uint8_t identity_pubkey[32], uint64_t timestamp, uint8_t capabilities, uint8_t max_participants, const uint8_t signature[64])
Verify SESSION_CREATE signature.
asciichat_error_t acds_session_lookup(acds_client_t *client, const char *session_string, acds_session_lookup_result_t *result)
Look up session by string.
asciichat_error_t acds_verify_session_join(const uint8_t identity_pubkey[32], uint64_t timestamp, const char *session_string, const uint8_t signature[64])
Verify SESSION_JOIN signature.
void acds_client_config_init_defaults(acds_client_config_t *config)
Initialize ACDS client configuration with defaults.
Definition acds_client.c:38
asciichat_error_t acds_session_create(acds_client_t *client, const acds_session_create_params_t *params, acds_session_create_result_t *result)
Create a new session on the discovery server.
asciichat_error_t acds_sign_session_join(const uint8_t identity_seckey[64], uint64_t timestamp, const char *session_string, uint8_t signature_out[64])
Sign a SESSION_JOIN message.
asciichat_error_t acds_session_join(acds_client_t *client, const acds_session_join_params_t *params, acds_session_join_result_t *result)
Join an existing session.
bool acds_validate_timestamp(uint64_t timestamp_ms, uint32_t window_seconds)
Check if timestamp is within acceptable window.
asciichat_error_t acds_client_connect(acds_client_t *client, const acds_client_config_t *config)
Connect to ACDS server.
Definition acds_client.c:53
asciichat_error_t acds_sign_session_create(const uint8_t identity_seckey[64], uint64_t timestamp, uint8_t capabilities, uint8_t max_participants, uint8_t signature_out[64])
Sign a SESSION_CREATE message.
void acds_client_disconnect(acds_client_t *client)
Disconnect from ACDS server.
🗃️ Lock-Free Unified Memory Buffer Pool with Lazy Allocation
🔄 Network byte order conversion helpers
acip_session_join_t
acip_session_joined_t
acip_session_lookup_t
#define ACIP_DISCOVERY_DEFAULT_PORT
Discovery server default port.
acip_session_created_t
acip_session_create_t
acip_session_info_t
@ SESSION_TYPE_WEBRTC
WebRTC P2P mesh with STUN/TURN relay.
void buffer_pool_free(buffer_pool_t *pool, void *data, size_t size)
Free a buffer back to the pool (lock-free)
unsigned int uint32_t
Definition common.h:58
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:358
unsigned long long uint64_t
Definition common.h:59
unsigned char uint8_t
Definition common.h:56
#define SET_ERRNO_SYS(code, context_msg,...)
Set error code with custom message and system error context.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:46
@ ERROR_NETWORK
Definition error_codes.h:69
@ ERROR_CRYPTO_VERIFICATION
Definition error_codes.h:92
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_CRYPTO
Definition error_codes.h:88
@ ERROR_INVALID_PARAM
#define log_warn(...)
Log a WARN message.
#define log_error(...)
Log an ERROR message.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
int receive_packet(socket_t sockfd, packet_type_t *type, void **data, size_t *len)
Receive a basic packet without encryption.
Definition packet.c:767
int send_packet(socket_t sockfd, packet_type_t type, const void *data, size_t len)
Send a basic packet without encryption.
Definition packet.c:754
packet_type_t
Network protocol packet type enumeration.
Definition packet.h:281
@ PACKET_TYPE_ACIP_SESSION_INFO
Session info response (Discovery Server -> Client)
Definition packet.h:375
@ PACKET_TYPE_ACIP_ERROR
Generic error response (Discovery Server -> Client)
Definition packet.h:404
@ PACKET_TYPE_ACIP_SESSION_CREATE
Create new session (Client -> Discovery Server)
Definition packet.h:369
@ PACKET_TYPE_ACIP_SESSION_LOOKUP
Lookup session by string (Client -> Discovery Server)
Definition packet.h:373
@ PACKET_TYPE_ACIP_SESSION_JOIN
Join existing session (Client -> Discovery Server)
Definition packet.h:377
@ PACKET_TYPE_ACIP_SESSION_CREATED
Session created response (Discovery Server -> Client)
Definition packet.h:371
@ PACKET_TYPE_ERROR_MESSAGE
Error packet with asciichat_error_t code and human-readable message.
Definition packet.h:352
@ PACKET_TYPE_ACIP_SESSION_JOINED
Session joined response (Discovery Server -> Client)
Definition packet.h:379
#define INVALID_SOCKET_VALUE
Invalid socket value (POSIX: -1)
Definition socket.h:52
int socket_close(socket_t sock)
Close a socket.
int socket_set_timeout(socket_t sock, uint32_t timeout_ms)
Set socket receive and send timeouts.
Definition socket.c:81
const char * socket_get_error_string(void)
Get last socket error as string.
📝 Logging API with multiple log levels and terminal output control
Packet protocol implementation with encryption and compression support.
Cross-platform socket interface for ascii-chat.
ACDS client connection configuration.
Definition acds_client.h:44
char server_address[256]
ACDS server address (e.g., "discovery.ascii.chat" or "127.0.0.1")
Definition acds_client.h:45
uint32_t timeout_ms
Connection timeout in milliseconds.
Definition acds_client.h:47
uint16_t server_port
ACDS server port (default: 27225)
Definition acds_client.h:46
ACDS client connection handle.
Definition acds_client.h:53
acds_client_config_t config
Definition acds_client.h:54
bool connected
Connection status.
Definition acds_client.h:56
socket_t socket
TCP socket to ACDS server.
Definition acds_client.h:55
Session creation request parameters.
Definition acds_client.h:90
const char * reserved_string
Optional reserved string (NULL = auto-generate)
Definition acds_client.h:99
uint16_t server_port
Server port where clients should connect.
bool acds_expose_ip
Explicitly allow public IP disclosure (–acds-expose-ip opt-in)
Definition acds_client.h:97
uint8_t identity_seckey[64]
Ed25519 secret key (for signing)
Definition acds_client.h:92
uint8_t max_participants
Maximum participants (1-8)
Definition acds_client.h:94
uint8_t identity_pubkey[32]
Ed25519 public key (host identity)
Definition acds_client.h:91
char server_address[64]
Server address where clients should connect.
bool has_password
Password protection enabled.
Definition acds_client.h:95
uint8_t capabilities
Bit 0: video, Bit 1: audio.
Definition acds_client.h:93
char password[128]
Optional password (if has_password)
Definition acds_client.h:96
uint8_t session_type
acds_session_type_t: 0=DIRECT_TCP (default), 1=WEBRTC
Definition acds_client.h:98
Session creation result.
uint8_t session_id[16]
Session UUID.
char session_string[49]
Generated session string (null-terminated)
uint64_t expires_at
Expiration timestamp (Unix ms)
Session join parameters.
uint8_t identity_pubkey[32]
Participant's Ed25519 public key.
bool has_password
Password provided.
char password[128]
Password (if has_password)
const char * session_string
Session to join.
uint8_t identity_seckey[64]
Ed25519 secret key (for signing)
Session join result.
char error_message[129]
Error message (if !success, null-terminated)
uint8_t error_code
Error code (if !success)
uint8_t session_type
acds_session_type_t: 0=DIRECT_TCP, 1=WEBRTC (if success)
uint8_t participant_id[16]
Participant UUID (if success)
bool success
Join succeeded.
char server_address[65]
Server IP/hostname (if success, null-terminated)
uint16_t server_port
Server port (if success)
uint8_t session_id[16]
Session UUID (if success)
Session lookup result.
uint8_t capabilities
Session capabilities.
bool found
Session exists.
bool require_client_verify
ACDS policy: client must verify server identity.
bool has_password
Password required to join.
uint64_t expires_at
Expiration timestamp (Unix ms)
uint8_t max_participants
Maximum participants.
bool require_server_verify
ACDS policy: server must verify client identity.
uint64_t created_at
Creation timestamp (Unix ms)
uint8_t host_pubkey[32]
Host's Ed25519 public key.
uint8_t current_participants
Current participant count.
uint8_t session_id[16]
Session UUID (if found)
⏱️ High-precision timing utilities using sokol_time.h and uthash