ascii-chat 0.8.38
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
15#include <ascii-chat/network/acip/acds_client.h>
16#include <ascii-chat/buffer_pool.h>
17#include <ascii-chat/common.h>
18#include <ascii-chat/crypto/crypto.h>
19#include <ascii-chat/log/logging.h>
20#include <ascii-chat/network/packet.h>
21#include <ascii-chat/network/parallel_connect.h>
22#include <ascii-chat/platform/socket.h>
23#include <ascii-chat/util/endian.h>
24#include <ascii-chat/util/time.h>
25
26#include <string.h>
27#include <time.h>
28#include <errno.h>
29
30#ifdef _WIN32
31#include <winsock2.h>
32#include <ws2tcpip.h>
33#else
34#include <netdb.h>
35#include <sys/socket.h>
36#include <sys/types.h>
37#include <sys/select.h>
38#include <fcntl.h>
39#endif
40
41// ============================================================================
42// Helper Functions
43// ============================================================================
44
45void acds_client_config_init_defaults(acds_client_config_t *config) {
46 if (!config) {
47 return;
48 }
49
50 memset(config, 0, sizeof(*config));
51 SAFE_STRNCPY(config->server_address, "127.0.0.1", sizeof(config->server_address));
52 config->server_port = ACIP_DISCOVERY_DEFAULT_PORT;
53 config->timeout_ms = 5 * MS_PER_SEC_INT;
54}
55
56// ============================================================================
57// Connection Management
58// ============================================================================
59
60asciichat_error_t acds_client_connect(acds_client_t *client, const acds_client_config_t *config) {
61 if (!client || !config) {
62 return SET_ERRNO(ERROR_INVALID_PARAM, "client or config is NULL");
63 }
64
65 memset(client, 0, sizeof(*client));
66 memcpy(&client->config, config, sizeof(acds_client_config_t));
67 client->socket = INVALID_SOCKET_VALUE;
68 client->connected = false;
69
70 // Resolve server address (supports both hostnames and IP addresses, IPv4 and IPv6)
71 struct addrinfo hints;
72 struct addrinfo *result = NULL;
73 memset(&hints, 0, sizeof(hints));
74 hints.ai_family = AF_UNSPEC; // IPv4 or IPv6
75 hints.ai_socktype = SOCK_STREAM; // TCP
76
77 char port_str[16];
78 safe_snprintf(port_str, sizeof(port_str), "%d", config->server_port);
79
80 log_debug("ACDS: Resolving address '%s:%s'...", config->server_address, port_str);
81 int gai_result = getaddrinfo(config->server_address, port_str, &hints, &result);
82 if (gai_result != 0) {
83 return SET_ERRNO(ERROR_NETWORK, "Failed to resolve server address '%s': %s", config->server_address,
84 gai_strerror(gai_result));
85 }
86 log_debug("ACDS: Address resolved successfully");
87 freeaddrinfo(result);
88
89 // Use parallel_connect to attempt IPv4 and IPv6 connections concurrently
90 parallel_connect_config_t pconn_config = {
91 .hostname = config->server_address,
92 .port = config->server_port,
93 .timeout_ms = config->timeout_ms,
94 .should_exit_callback = config->should_exit_callback,
95 .callback_data = config->callback_data,
96 };
97
98 asciichat_error_t pconn_result = parallel_connect(&pconn_config, &client->socket);
99 if (pconn_result == ASCIICHAT_OK) {
100 client->connected = true;
101 log_info("Connected to ACDS server at %s:%d", config->server_address, config->server_port);
102 return ASCIICHAT_OK;
103 }
104
105 return pconn_result;
106}
107
108void acds_client_disconnect(acds_client_t *client) {
109 if (!client) {
110 return;
111 }
112
113 if (client->socket != INVALID_SOCKET_VALUE) {
114 socket_close(client->socket);
115 client->socket = INVALID_SOCKET_VALUE;
116 }
117
118 client->connected = false;
119 log_debug("Disconnected from ACDS server");
120}
121
122// ============================================================================
123// Session Management
124// ============================================================================
125
126asciichat_error_t acds_session_create(acds_client_t *client, const acds_session_create_params_t *params,
127 acds_session_create_result_t *result) {
128 if (!client || !params || !result) {
129 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
130 }
131
132 if (!client->connected) {
133 return SET_ERRNO(ERROR_NETWORK, "Not connected to ACDS server");
134 }
135
136 // Build SESSION_CREATE payload
137 acip_session_create_t req;
138 memset(&req, 0, sizeof(req));
139
140 memcpy(req.identity_pubkey, params->identity_pubkey, 32);
141
142 // Sign the request: type || timestamp || capabilities
143 uint64_t timestamp = (uint64_t)time(NULL) * 1000; // Unix ms
144 req.timestamp = timestamp;
145 req.capabilities = params->capabilities;
146 req.max_participants = params->max_participants;
147
148 // Generate Ed25519 signature for identity verification
149 asciichat_error_t sign_result = acds_sign_session_create(params->identity_seckey, timestamp, params->capabilities,
150 params->max_participants, req.signature);
151 if (sign_result != ASCIICHAT_OK) {
152 return SET_ERRNO(ERROR_CRYPTO, "Failed to sign SESSION_CREATE request");
153 }
154
155 req.has_password = params->has_password ? 1 : 0;
156 if (params->has_password) {
157 // Hash password with Argon2id using libsodium
158 // crypto_pwhash_str produces a null-terminated ASCII string
159 if (crypto_pwhash_str((char *)req.password_hash, params->password, strlen(params->password),
160 crypto_pwhash_OPSLIMIT_INTERACTIVE, crypto_pwhash_MEMLIMIT_INTERACTIVE) != 0) {
161 return SET_ERRNO(ERROR_CRYPTO, "Failed to hash password (out of memory)");
162 }
163 } else {
164 memset(req.password_hash, 0, sizeof(req.password_hash));
165 }
166
167 req.reserved_string_len = 0; // Auto-generate
168 if (params->reserved_string && params->reserved_string[0] != '\0') {
169 req.reserved_string_len = strlen(params->reserved_string);
170 // Variable part would follow here (not implemented yet)
171 }
172
173 // Server connection information
174 SAFE_STRNCPY(req.server_address, params->server_address, sizeof(req.server_address));
175 req.server_port = params->server_port;
176
177 // IP disclosure policy
178 // Auto-detection: If password is set, IP will be revealed after verification
179 // If no password, require explicit opt-in via acds_expose_ip
180 req.expose_ip_publicly = params->acds_expose_ip ? 1 : 0;
181
182 // Session type (Direct TCP or WebRTC)
183 req.session_type = params->session_type;
184
185 // Send SESSION_CREATE packet
186 asciichat_error_t send_result = send_packet(client->socket, PACKET_TYPE_ACIP_SESSION_CREATE, &req, sizeof(req));
187 if (send_result != ASCIICHAT_OK) {
188 return send_result;
189 }
190
191 log_debug("Sent SESSION_CREATE request");
192 log_debug("★ ACDS_CLIENT: About to receive SESSION_CREATED on socket %d", client->socket);
193
194 // Receive SESSION_CREATED response
195 packet_type_t resp_type;
196 void *resp_payload = NULL;
197 size_t resp_size = 0;
198
199 int recv_result = receive_packet(client->socket, &resp_type, &resp_payload, &resp_size);
200 log_debug("★ ACDS_CLIENT: receive_packet returned %d", recv_result);
201 if (recv_result < 0) {
202 return SET_ERRNO(ERROR_NETWORK, "Failed to receive SESSION_CREATED response");
203 }
204
205 // Check response type
206 if (resp_type != PACKET_TYPE_ACIP_SESSION_CREATED) {
207 if (resp_type == PACKET_TYPE_ERROR_MESSAGE || resp_type == PACKET_TYPE_ACIP_ERROR) {
208 log_error("Session creation failed: server returned error packet");
209 buffer_pool_free(NULL, resp_payload, resp_size);
210 return SET_ERRNO(ERROR_NETWORK, "Session creation failed");
211 }
212 buffer_pool_free(NULL, resp_payload, resp_size);
213 return SET_ERRNO(ERROR_NETWORK, "Unexpected response type: 0x%02X", resp_type);
214 }
215
216 // Parse SESSION_CREATED response
217 if (resp_size < sizeof(acip_session_created_t)) {
218 buffer_pool_free(NULL, resp_payload, resp_size);
219 return SET_ERRNO(ERROR_NETWORK, "SESSION_CREATED response too small: %zu bytes", resp_size);
220 }
221
222 acip_session_created_t *resp = (acip_session_created_t *)resp_payload;
223
224 // Copy session string (null-terminate)
225 size_t string_len = resp->session_string_len < sizeof(result->session_string) - 1
226 ? resp->session_string_len
227 : sizeof(result->session_string) - 1;
228 memcpy(result->session_string, resp->session_string, string_len);
229 result->session_string[string_len] = '\0';
230
231 memcpy(result->session_id, resp->session_id, 16);
232 result->expires_at = resp->expires_at;
233
234 log_info("Session created: %s (expires at %llu)", result->session_string, (unsigned long long)result->expires_at);
235
236 buffer_pool_free(NULL, resp_payload, resp_size);
237 return ASCIICHAT_OK;
238}
239
240asciichat_error_t acds_session_lookup(acds_client_t *client, const char *session_string,
241 acds_session_lookup_result_t *result) {
242 if (!client || !session_string || !result) {
243 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
244 }
245
246 if (!client->connected) {
247 return SET_ERRNO(ERROR_NETWORK, "Not connected to ACDS server");
248 }
249
250 // Build SESSION_LOOKUP payload
251 acip_session_lookup_t req;
252 memset(&req, 0, sizeof(req));
253
254 req.session_string_len = strlen(session_string);
255 if (req.session_string_len >= sizeof(req.session_string)) {
256 return SET_ERRNO(ERROR_INVALID_PARAM, "Session string too long");
257 }
258
259 memcpy(req.session_string, session_string, req.session_string_len);
260
261 // Send SESSION_LOOKUP packet
262 asciichat_error_t send_result = send_packet(client->socket, PACKET_TYPE_ACIP_SESSION_LOOKUP, &req, sizeof(req));
263 if (send_result != ASCIICHAT_OK) {
264 return send_result;
265 }
266
267 log_debug("Sent SESSION_LOOKUP request for '%s'", session_string);
268
269 // Receive SESSION_INFO response
270 packet_type_t resp_type;
271 void *resp_payload = NULL;
272 size_t resp_size = 0;
273
274 int recv_result = receive_packet(client->socket, &resp_type, &resp_payload, &resp_size);
275 if (recv_result < 0) {
276 return SET_ERRNO(ERROR_NETWORK, "Failed to receive SESSION_INFO response");
277 }
278
279 if (resp_type != PACKET_TYPE_ACIP_SESSION_INFO) {
280 buffer_pool_free(NULL, resp_payload, resp_size);
281 return SET_ERRNO(ERROR_NETWORK, "Unexpected response type: 0x%02X", resp_type);
282 }
283
284 if (resp_size < sizeof(acip_session_info_t)) {
285 buffer_pool_free(NULL, resp_payload, resp_size);
286 return SET_ERRNO(ERROR_NETWORK, "SESSION_INFO response too small");
287 }
288
289 acip_session_info_t *resp = (acip_session_info_t *)resp_payload;
290
291 // Copy result
292 result->found = resp->found != 0;
293 if (result->found) {
294 memcpy(result->session_id, resp->session_id, 16);
295 memcpy(result->host_pubkey, resp->host_pubkey, 32);
296 result->capabilities = resp->capabilities;
297 result->max_participants = resp->max_participants;
298 result->current_participants = resp->current_participants;
299 result->has_password = resp->has_password != 0;
300 result->created_at = resp->created_at;
301 result->expires_at = resp->expires_at;
302 result->require_server_verify = resp->require_server_verify != 0;
303 result->require_client_verify = resp->require_client_verify != 0;
304
305 log_info("Session found: %s (%d/%d participants, password=%s, policies: server_verify=%d client_verify=%d)",
306 session_string, result->current_participants, result->max_participants,
307 result->has_password ? "required" : "not required", result->require_server_verify,
308 result->require_client_verify);
309 } else {
310 log_info("Session not found: %s", session_string);
311 }
312
313 buffer_pool_free(NULL, resp_payload, resp_size);
314 return ASCIICHAT_OK;
315}
316
317asciichat_error_t acds_session_join(acds_client_t *client, const acds_session_join_params_t *params,
318 acds_session_join_result_t *result) {
319 if (!client || !params || !result) {
320 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
321 }
322
323 if (!client->connected) {
324 return SET_ERRNO(ERROR_NETWORK, "Not connected to ACDS server");
325 }
326
327 // Build SESSION_JOIN payload
328 acip_session_join_t req;
329 memset(&req, 0, sizeof(req));
330
331 req.session_string_len = strlen(params->session_string);
332 if (req.session_string_len >= sizeof(req.session_string)) {
333 return SET_ERRNO(ERROR_INVALID_PARAM, "Session string too long");
334 }
335
336 memcpy(req.session_string, params->session_string, req.session_string_len);
337 memcpy(req.identity_pubkey, params->identity_pubkey, 32);
338
339 // Sign the request: type || timestamp || session_string
340 uint64_t timestamp = (uint64_t)time(NULL) * 1000;
341 req.timestamp = timestamp;
342
343 // Generate Ed25519 signature for identity verification
344 asciichat_error_t sign_result =
345 acds_sign_session_join(params->identity_seckey, timestamp, params->session_string, req.signature);
346 if (sign_result != ASCIICHAT_OK) {
347 return SET_ERRNO(ERROR_CRYPTO, "Failed to sign SESSION_JOIN request");
348 }
349
350 req.has_password = params->has_password ? 1 : 0;
351 if (params->has_password) {
352 SAFE_STRNCPY(req.password, params->password, sizeof(req.password));
353 }
354
355 // Send SESSION_JOIN packet
356 asciichat_error_t send_result = send_packet(client->socket, PACKET_TYPE_ACIP_SESSION_JOIN, &req, sizeof(req));
357 if (send_result != ASCIICHAT_OK) {
358 return send_result;
359 }
360
361 log_debug("Sent SESSION_JOIN request for '%s'", params->session_string);
362
363 // Receive SESSION_JOINED response
364 packet_type_t resp_type;
365 void *resp_payload = NULL;
366 size_t resp_size = 0;
367
368 int recv_result = receive_packet(client->socket, &resp_type, &resp_payload, &resp_size);
369 if (recv_result < 0) {
370 return SET_ERRNO(ERROR_NETWORK, "Failed to receive SESSION_JOINED response");
371 }
372
373 if (resp_type != PACKET_TYPE_ACIP_SESSION_JOINED) {
374 buffer_pool_free(NULL, resp_payload, resp_size);
375 return SET_ERRNO(ERROR_NETWORK, "Unexpected response type: 0x%02X", resp_type);
376 }
377
378 if (resp_size < sizeof(acip_session_joined_t)) {
379 buffer_pool_free(NULL, resp_payload, resp_size);
380 return SET_ERRNO(ERROR_NETWORK, "SESSION_JOINED response too small");
381 }
382
383 acip_session_joined_t *resp = (acip_session_joined_t *)resp_payload;
384
385 // Copy result
386 result->success = resp->success != 0;
387 if (result->success) {
388 memcpy(result->participant_id, resp->participant_id, 16);
389 memcpy(result->session_id, resp->session_id, 16);
390 // Server connection information (ONLY revealed after successful authentication)
391 result->session_type = resp->session_type;
392 SAFE_STRNCPY(result->server_address, resp->server_address, sizeof(result->server_address));
393 result->server_port = resp->server_port;
394 log_info("Joined session successfully (participant ID: %02x%02x..., server=%s:%d, type=%s)",
395 result->participant_id[0], result->participant_id[1], result->server_address, result->server_port,
396 result->session_type == SESSION_TYPE_WEBRTC ? "WebRTC" : "DirectTCP");
397 } else {
398 result->error_code = resp->error_code;
399 size_t msg_len = strnlen(resp->error_message, sizeof(resp->error_message));
400 if (msg_len >= sizeof(result->error_message)) {
401 msg_len = sizeof(result->error_message) - 1;
402 }
403 memcpy(result->error_message, resp->error_message, msg_len);
404 result->error_message[msg_len] = '\0';
405 log_warn("Failed to join session: %s (code %d)", result->error_message, result->error_code);
406 }
407
408 buffer_pool_free(NULL, resp_payload, resp_size);
409 return ASCIICHAT_OK;
410}
411
412// ============================================================================
413// Cryptographic Signature Helpers
414// ============================================================================
415
416asciichat_error_t acds_sign_session_create(const uint8_t identity_seckey[64], uint64_t timestamp, uint8_t capabilities,
417 uint8_t max_participants, uint8_t signature_out[64]) {
418 if (!identity_seckey || !signature_out) {
419 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL parameter to acds_sign_session_create");
420 }
421
422 // Build message: type (1 byte) || timestamp (8 bytes) || capabilities (1 byte) || max_participants (1 byte)
423 uint8_t message[11];
424 message[0] = (uint8_t)PACKET_TYPE_ACIP_SESSION_CREATE;
425
426 // Convert timestamp to big-endian (network byte order)
427 message[1] = (uint8_t)(timestamp >> 56);
428 message[2] = (uint8_t)(timestamp >> 48);
429 message[3] = (uint8_t)(timestamp >> 40);
430 message[4] = (uint8_t)(timestamp >> 32);
431 message[5] = (uint8_t)(timestamp >> 24);
432 message[6] = (uint8_t)(timestamp >> 16);
433 message[7] = (uint8_t)(timestamp >> 8);
434 message[8] = (uint8_t)(timestamp);
435
436 message[9] = capabilities;
437 message[10] = max_participants;
438
439 // Sign using Ed25519
440 if (crypto_sign_detached(signature_out, NULL, message, sizeof(message), identity_seckey) != 0) {
441 return SET_ERRNO(ERROR_CRYPTO, "Ed25519 signature generation failed");
442 }
443
444 log_debug("Generated SESSION_CREATE signature (timestamp=%llu, caps=%u, max=%u)", (unsigned long long)timestamp,
445 capabilities, max_participants);
446
447 return ASCIICHAT_OK;
448}
449
450asciichat_error_t acds_verify_session_create(const uint8_t identity_pubkey[32], uint64_t timestamp,
451 uint8_t capabilities, uint8_t max_participants,
452 const uint8_t signature[64]) {
453 if (!identity_pubkey || !signature) {
454 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL parameter to acds_verify_session_create");
455 }
456
457 // Build message: type (1 byte) || timestamp (8 bytes) || capabilities (1 byte) || max_participants (1 byte)
458 uint8_t message[11];
459 message[0] = (uint8_t)PACKET_TYPE_ACIP_SESSION_CREATE;
460
461 // Convert timestamp to big-endian (network byte order)
462 message[1] = (uint8_t)(timestamp >> 56);
463 message[2] = (uint8_t)(timestamp >> 48);
464 message[3] = (uint8_t)(timestamp >> 40);
465 message[4] = (uint8_t)(timestamp >> 32);
466 message[5] = (uint8_t)(timestamp >> 24);
467 message[6] = (uint8_t)(timestamp >> 16);
468 message[7] = (uint8_t)(timestamp >> 8);
469 message[8] = (uint8_t)(timestamp);
470
471 message[9] = capabilities;
472 message[10] = max_participants;
473
474 // Verify using Ed25519
475 if (crypto_sign_verify_detached(signature, message, sizeof(message), identity_pubkey) != 0) {
476 log_warn("SESSION_CREATE signature verification failed");
477 return SET_ERRNO(ERROR_CRYPTO_VERIFICATION, "Invalid SESSION_CREATE signature");
478 }
479
480 log_debug("SESSION_CREATE signature verified successfully");
481 return ASCIICHAT_OK;
482}
483
484asciichat_error_t acds_sign_session_join(const uint8_t identity_seckey[64], uint64_t timestamp,
485 const char *session_string, uint8_t signature_out[64]) {
486 if (!identity_seckey || !session_string || !signature_out) {
487 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL parameter to acds_sign_session_join");
488 }
489
490 size_t session_len = strlen(session_string);
491 if (session_len > 48) {
492 return SET_ERRNO(ERROR_INVALID_PARAM, "Session string too long (max 48 chars)");
493 }
494
495 // Build message: type (1 byte) || timestamp (8 bytes) || session_string (variable length)
496 uint8_t message[1 + 8 + 48];
497 message[0] = (uint8_t)PACKET_TYPE_ACIP_SESSION_JOIN;
498
499 // Convert timestamp to big-endian (network byte order)
500 message[1] = (uint8_t)(timestamp >> 56);
501 message[2] = (uint8_t)(timestamp >> 48);
502 message[3] = (uint8_t)(timestamp >> 40);
503 message[4] = (uint8_t)(timestamp >> 32);
504 message[5] = (uint8_t)(timestamp >> 24);
505 message[6] = (uint8_t)(timestamp >> 16);
506 message[7] = (uint8_t)(timestamp >> 8);
507 message[8] = (uint8_t)(timestamp);
508
509 // Copy session string (copy exactly session_len bytes, no null terminator in signature)
510 memcpy(&message[9], session_string, session_len);
511
512 size_t message_len = 9 + session_len;
513
514 // Sign using Ed25519
515 if (crypto_sign_detached(signature_out, NULL, message, message_len, identity_seckey) != 0) {
516 return SET_ERRNO(ERROR_CRYPTO, "Ed25519 signature generation failed");
517 }
518
519 log_debug("Generated SESSION_JOIN signature (timestamp=%llu, session='%s')", (unsigned long long)timestamp,
520 session_string);
521
522 return ASCIICHAT_OK;
523}
524
525asciichat_error_t acds_verify_session_join(const uint8_t identity_pubkey[32], uint64_t timestamp,
526 const char *session_string, const uint8_t signature[64]) {
527 if (!identity_pubkey || !session_string || !signature) {
528 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL parameter to acds_verify_session_join");
529 }
530
531 size_t session_len = strlen(session_string);
532 if (session_len > 48) {
533 return SET_ERRNO(ERROR_INVALID_PARAM, "Session string too long (max 48 chars)");
534 }
535
536 // Build message: type (1 byte) || timestamp (8 bytes) || session_string (variable length)
537 uint8_t message[1 + 8 + 48];
538 message[0] = (uint8_t)PACKET_TYPE_ACIP_SESSION_JOIN;
539
540 // Convert timestamp to big-endian (network byte order)
541 message[1] = (uint8_t)(timestamp >> 56);
542 message[2] = (uint8_t)(timestamp >> 48);
543 message[3] = (uint8_t)(timestamp >> 40);
544 message[4] = (uint8_t)(timestamp >> 32);
545 message[5] = (uint8_t)(timestamp >> 24);
546 message[6] = (uint8_t)(timestamp >> 16);
547 message[7] = (uint8_t)(timestamp >> 8);
548 message[8] = (uint8_t)(timestamp);
549
550 // Copy session string (copy exactly session_len bytes, no null terminator in signature)
551 memcpy(&message[9], session_string, session_len);
552
553 size_t message_len = 9 + session_len;
554
555 // Verify using Ed25519
556 if (crypto_sign_verify_detached(signature, message, message_len, identity_pubkey) != 0) {
557 log_warn("SESSION_JOIN signature verification failed");
558 return SET_ERRNO(ERROR_CRYPTO_VERIFICATION, "Invalid SESSION_JOIN signature");
559 }
560
561 log_debug("SESSION_JOIN signature verified successfully");
562 return ASCIICHAT_OK;
563}
564
565bool acds_validate_timestamp(uint64_t timestamp_ms, uint32_t window_seconds) {
566 uint64_t now_ms = time_ns_to_ms(time_get_realtime_ns());
567 uint64_t window_ms = (uint64_t)window_seconds * 1000;
568
569 if (timestamp_ms > now_ms + 60000) {
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 uint64_t min_valid_timestamp = (now_ms >= window_ms) ? (now_ms - window_ms) : 0;
577 if (timestamp_ms < min_valid_timestamp) {
578 int64_t age = (int64_t)now_ms - (int64_t)timestamp_ms;
579 log_warn("Timestamp is too old: %llu < %llu (age: %lld ms, max: %u seconds)", (unsigned long long)timestamp_ms,
580 (unsigned long long)min_valid_timestamp, (long long)age, window_seconds);
581 return false;
582 }
583
584 int64_t age = (int64_t)now_ms - (int64_t)timestamp_ms;
585 log_debug("Timestamp validation passed (age: %lld ms, window: %u seconds)", (long long)age, window_seconds);
586 return true;
587}
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])
asciichat_error_t acds_session_lookup(acds_client_t *client, const char *session_string, acds_session_lookup_result_t *result)
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])
void acds_client_config_init_defaults(acds_client_config_t *config)
Definition acds_client.c:45
asciichat_error_t acds_session_create(acds_client_t *client, const acds_session_create_params_t *params, acds_session_create_result_t *result)
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])
asciichat_error_t acds_session_join(acds_client_t *client, const acds_session_join_params_t *params, acds_session_join_result_t *result)
bool acds_validate_timestamp(uint64_t timestamp_ms, uint32_t window_seconds)
asciichat_error_t acds_client_connect(acds_client_t *client, const acds_client_config_t *config)
Definition acds_client.c:60
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])
void acds_client_disconnect(acds_client_t *client)
void buffer_pool_free(buffer_pool_t *pool, void *data, size_t size)
int receive_packet(socket_t sockfd, packet_type_t *type, void **data, size_t *len)
Receive a basic packet without encryption.
Definition packet.c:766
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:753
asciichat_error_t parallel_connect(const parallel_connect_config_t *config, socket_t *out_socket)
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456
uint64_t time_get_realtime_ns(void)
Definition util/time.c:59