61 if (!client || !config) {
62 return SET_ERRNO(ERROR_INVALID_PARAM,
"client or config is NULL");
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;
71 struct addrinfo hints;
72 struct addrinfo *result = NULL;
73 memset(&hints, 0,
sizeof(hints));
74 hints.ai_family = AF_UNSPEC;
75 hints.ai_socktype = SOCK_STREAM;
78 safe_snprintf(port_str,
sizeof(port_str),
"%d", config->server_port);
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));
86 log_debug(
"ACDS: Address resolved successfully");
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,
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);
127 acds_session_create_result_t *result) {
128 if (!client || !params || !result) {
129 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters");
132 if (!client->connected) {
133 return SET_ERRNO(ERROR_NETWORK,
"Not connected to ACDS server");
137 acip_session_create_t req;
138 memset(&req, 0,
sizeof(req));
140 memcpy(req.identity_pubkey, params->identity_pubkey, 32);
143 uint64_t timestamp = (uint64_t)time(NULL) * 1000;
144 req.timestamp = timestamp;
145 req.capabilities = params->capabilities;
146 req.max_participants = params->max_participants;
150 params->max_participants, req.signature);
151 if (sign_result != ASCIICHAT_OK) {
152 return SET_ERRNO(ERROR_CRYPTO,
"Failed to sign SESSION_CREATE request");
155 req.has_password = params->has_password ? 1 : 0;
156 if (params->has_password) {
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)");
164 memset(req.password_hash, 0,
sizeof(req.password_hash));
167 req.reserved_string_len = 0;
168 if (params->reserved_string && params->reserved_string[0] !=
'\0') {
169 req.reserved_string_len = strlen(params->reserved_string);
174 SAFE_STRNCPY(req.server_address, params->server_address,
sizeof(req.server_address));
175 req.server_port = params->server_port;
180 req.expose_ip_publicly = params->acds_expose_ip ? 1 : 0;
183 req.session_type = params->session_type;
186 asciichat_error_t send_result =
send_packet(client->socket, PACKET_TYPE_ACIP_SESSION_CREATE, &req,
sizeof(req));
187 if (send_result != ASCIICHAT_OK) {
191 log_debug(
"Sent SESSION_CREATE request");
192 log_debug(
"★ ACDS_CLIENT: About to receive SESSION_CREATED on socket %d", client->socket);
195 packet_type_t resp_type;
196 void *resp_payload = NULL;
197 size_t resp_size = 0;
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");
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");
210 return SET_ERRNO(ERROR_NETWORK,
"Session creation failed");
213 return SET_ERRNO(ERROR_NETWORK,
"Unexpected response type: 0x%02X", resp_type);
217 if (resp_size <
sizeof(acip_session_created_t)) {
219 return SET_ERRNO(ERROR_NETWORK,
"SESSION_CREATED response too small: %zu bytes", resp_size);
222 acip_session_created_t *resp = (acip_session_created_t *)resp_payload;
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';
231 memcpy(result->session_id, resp->session_id, 16);
232 result->expires_at = resp->expires_at;
234 log_info(
"Session created: %s (expires at %llu)", result->session_string, (
unsigned long long)result->expires_at);
241 acds_session_lookup_result_t *result) {
242 if (!client || !session_string || !result) {
243 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters");
246 if (!client->connected) {
247 return SET_ERRNO(ERROR_NETWORK,
"Not connected to ACDS server");
251 acip_session_lookup_t req;
252 memset(&req, 0,
sizeof(req));
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");
259 memcpy(req.session_string, session_string, req.session_string_len);
262 asciichat_error_t send_result =
send_packet(client->socket, PACKET_TYPE_ACIP_SESSION_LOOKUP, &req,
sizeof(req));
263 if (send_result != ASCIICHAT_OK) {
267 log_debug(
"Sent SESSION_LOOKUP request for '%s'", session_string);
270 packet_type_t resp_type;
271 void *resp_payload = NULL;
272 size_t resp_size = 0;
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");
279 if (resp_type != PACKET_TYPE_ACIP_SESSION_INFO) {
281 return SET_ERRNO(ERROR_NETWORK,
"Unexpected response type: 0x%02X", resp_type);
284 if (resp_size <
sizeof(acip_session_info_t)) {
286 return SET_ERRNO(ERROR_NETWORK,
"SESSION_INFO response too small");
289 acip_session_info_t *resp = (acip_session_info_t *)resp_payload;
292 result->found = resp->found != 0;
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;
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);
310 log_info(
"Session not found: %s", session_string);
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");
323 if (!client->connected) {
324 return SET_ERRNO(ERROR_NETWORK,
"Not connected to ACDS server");
328 acip_session_join_t req;
329 memset(&req, 0,
sizeof(req));
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");
336 memcpy(req.session_string, params->session_string, req.session_string_len);
337 memcpy(req.identity_pubkey, params->identity_pubkey, 32);
340 uint64_t timestamp = (uint64_t)time(NULL) * 1000;
341 req.timestamp = timestamp;
344 asciichat_error_t sign_result =
346 if (sign_result != ASCIICHAT_OK) {
347 return SET_ERRNO(ERROR_CRYPTO,
"Failed to sign SESSION_JOIN request");
350 req.has_password = params->has_password ? 1 : 0;
351 if (params->has_password) {
352 SAFE_STRNCPY(req.password, params->password,
sizeof(req.password));
356 asciichat_error_t send_result =
send_packet(client->socket, PACKET_TYPE_ACIP_SESSION_JOIN, &req,
sizeof(req));
357 if (send_result != ASCIICHAT_OK) {
361 log_debug(
"Sent SESSION_JOIN request for '%s'", params->session_string);
364 packet_type_t resp_type;
365 void *resp_payload = NULL;
366 size_t resp_size = 0;
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");
373 if (resp_type != PACKET_TYPE_ACIP_SESSION_JOINED) {
375 return SET_ERRNO(ERROR_NETWORK,
"Unexpected response type: 0x%02X", resp_type);
378 if (resp_size <
sizeof(acip_session_joined_t)) {
380 return SET_ERRNO(ERROR_NETWORK,
"SESSION_JOINED response too small");
383 acip_session_joined_t *resp = (acip_session_joined_t *)resp_payload;
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);
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");
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;
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);
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");
424 message[0] = (uint8_t)PACKET_TYPE_ACIP_SESSION_CREATE;
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);
436 message[9] = capabilities;
437 message[10] = max_participants;
440 if (crypto_sign_detached(signature_out, NULL, message,
sizeof(message), identity_seckey) != 0) {
441 return SET_ERRNO(ERROR_CRYPTO,
"Ed25519 signature generation failed");
444 log_debug(
"Generated SESSION_CREATE signature (timestamp=%llu, caps=%u, max=%u)", (
unsigned long long)timestamp,
445 capabilities, max_participants);
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");
459 message[0] = (uint8_t)PACKET_TYPE_ACIP_SESSION_CREATE;
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);
471 message[9] = capabilities;
472 message[10] = max_participants;
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");
480 log_debug(
"SESSION_CREATE signature verified successfully");
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");
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)");
496 uint8_t message[1 + 8 + 48];
497 message[0] = (uint8_t)PACKET_TYPE_ACIP_SESSION_JOIN;
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);
510 memcpy(&message[9], session_string, session_len);
512 size_t message_len = 9 + session_len;
515 if (crypto_sign_detached(signature_out, NULL, message, message_len, identity_seckey) != 0) {
516 return SET_ERRNO(ERROR_CRYPTO,
"Ed25519 signature generation failed");
519 log_debug(
"Generated SESSION_JOIN signature (timestamp=%llu, session='%s')", (
unsigned long long)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");
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)");
537 uint8_t message[1 + 8 + 48];
538 message[0] = (uint8_t)PACKET_TYPE_ACIP_SESSION_JOIN;
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);
551 memcpy(&message[9], session_string, session_len);
553 size_t message_len = 9 + session_len;
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");
561 log_debug(
"SESSION_JOIN signature verified successfully");
567 uint64_t window_ms = (uint64_t)window_seconds * 1000;
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);
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);
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);