ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
acds_client.h File Reference

ACIP client-side protocol library and ACDS client connection API. More...

Go to the source code of this file.

Data Structures

struct  acds_client_config_t
 ACDS client connection configuration. More...
 
struct  acds_client_t
 ACDS client connection handle. More...
 
struct  acds_session_create_params_t
 Session creation request parameters. More...
 
struct  acds_session_create_result_t
 Session creation result. More...
 
struct  acds_session_lookup_result_t
 Session lookup result. More...
 
struct  acds_session_join_params_t
 Session join parameters. More...
 
struct  acds_session_join_result_t
 Session join result. More...
 

Typedefs

typedef bool(* parallel_connect_should_exit_fn) (void *user_data)
 

Functions

asciichat_error_t acds_client_connect (acds_client_t *client, const acds_client_config_t *config)
 Connect to ACDS server.
 
void acds_client_disconnect (acds_client_t *client)
 Disconnect from ACDS server.
 
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_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_session_join (acds_client_t *client, const acds_session_join_params_t *params, acds_session_join_result_t *result)
 Join an existing session.
 
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.
 
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_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_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.
 
bool acds_validate_timestamp (uint64_t timestamp_ms, uint32_t window_seconds)
 Check if timestamp is within acceptable window.
 
void acds_client_config_init_defaults (acds_client_config_t *config)
 Initialize ACDS client configuration with defaults.
 

Detailed Description

ACIP client-side protocol library and ACDS client connection API.

Definition in file acds_client.h.

Typedef Documentation

◆ parallel_connect_should_exit_fn

typedef bool(* parallel_connect_should_exit_fn) (void *user_data)

Definition at line 106 of file acds_client.h.

Function Documentation

◆ acds_client_config_init_defaults()

void acds_client_config_init_defaults ( acds_client_config_t *  config)

Initialize ACDS client configuration with defaults.

Sets default values:

  • server_address: "127.0.0.1"
  • server_port: 27225
  • timeout_ms: 5000
Parameters
configConfiguration to initialize

Definition at line 111 of file acds_client.c.

111 {
112 if (!config) {
113 return;
114 }
115
116 memset(config, 0, sizeof(*config));
117 SAFE_STRNCPY(config->server_address, "127.0.0.1", sizeof(config->server_address));
119 config->timeout_ms = 5 * MS_PER_SEC_INT;
120}
#define ACIP_DISCOVERY_DEFAULT_PORT
Discovery server default port (use OPT_ACDS_PORT_INT_DEFAULT from options.h)
Definition acds.h:1094
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define MS_PER_SEC_INT
Definition time.h:164
char server_address[256]
ACDS server address (e.g., "discovery.ascii.chat" or "127.0.0.1")
uint32_t timeout_ms
Connection timeout in milliseconds.
uint16_t server_port
ACDS server port (default: 27225)

References ACIP_DISCOVERY_DEFAULT_PORT, MS_PER_SEC_INT, SAFE_STRNCPY, acds_client_config_t::server_address, acds_client_config_t::server_port, and acds_client_config_t::timeout_ms.

Referenced by client_crypto_init().

◆ acds_client_connect()

asciichat_error_t acds_client_connect ( acds_client_t *  client,
const acds_client_config_t *  config 
)

Connect to ACDS server.

Establishes TCP connection to the discovery server.

Parameters
clientClient handle (output)
configConnection configuration
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 126 of file acds_client.c.

126 {
127 if (!client || !config) {
128 return SET_ERRNO(ERROR_INVALID_PARAM, "client or config is NULL");
129 }
130
131 memset(client, 0, sizeof(*client));
132 memcpy(&client->config, config, sizeof(acds_client_config_t));
134 client->connected = false;
135
136 // Resolve server address (supports both hostnames and IP addresses, IPv4 and IPv6)
137 struct addrinfo hints;
138 struct addrinfo *result = NULL;
139 memset(&hints, 0, sizeof(hints));
140 hints.ai_family = AF_UNSPEC; // IPv4 or IPv6
141 hints.ai_socktype = SOCK_STREAM; // TCP
142
143 char port_str[16];
144 safe_snprintf(port_str, sizeof(port_str), "%d", config->server_port);
145
146 log_debug("ACDS: Resolving address '%s:%s'...", config->server_address, port_str);
147 int gai_result = getaddrinfo(config->server_address, port_str, &hints, &result);
148 if (gai_result != 0) {
149 return SET_ERRNO(ERROR_NETWORK, "Failed to resolve server address '%s': %s", config->server_address,
150 gai_strerror(gai_result));
151 }
152 log_debug("ACDS: Address resolved successfully");
153 freeaddrinfo(result);
154
155 // Use parallel_connect to attempt IPv4 and IPv6 connections concurrently
156 parallel_connect_config_t pconn_config = {
157 .hostname = config->server_address,
158 .port = config->server_port,
159 .timeout_ms = config->timeout_ms,
160 .should_exit_callback = config->should_exit_callback,
161 .callback_data = config->callback_data,
162 };
163
164 asciichat_error_t pconn_result = parallel_connect(&pconn_config, &client->socket);
165 if (pconn_result == ASCIICHAT_OK) {
166 client->connected = true;
167
168 client->transport = acip_tcp_transport_create("acds_client_transport", client->socket, NULL);
169 if (!client->transport) {
170 socket_close(client->socket);
172 client->connected = false;
173 return SET_ERRNO(ERROR_NETWORK, "Failed to create ACDS transport");
174 }
175
176 asciichat_error_t handshake_result = acds_client_handshake(client);
177 if (handshake_result != ASCIICHAT_OK) {
179 client->transport = NULL;
180 socket_close(client->socket);
182 client->connected = false;
183 return handshake_result;
184 }
185
186 /* Register ACDS client with named registry */
187 char acds_name[64];
188 snprintf(acds_name, sizeof(acds_name), "acds_client:%s:%d", config->server_address, config->server_port);
189 NAMED_REGISTER(client, acds_name, "acds_client_t", "0x%tx", NULL);
190
191 log_info("Connected to ACDS server at %s:%d", config->server_address, config->server_port);
192 return ASCIICHAT_OK;
193 }
194
195 return pconn_result;
196}
#define NAMED_REGISTER(ptr, name, type, fmt, parent_ptr)
Register any pointer with base name, type, format spec, and location (auto-suffix)
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_NETWORK
Definition error_codes.h:77
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
#define INVALID_SOCKET_VALUE
Invalid socket value (POSIX: -1)
Definition socket.h:278
int socket_close(socket_t sock)
Close a socket.
asciichat_error_t parallel_connect(const parallel_connect_config_t *config, socket_t *out_socket)
ACDS client connection configuration.
parallel_connect_should_exit_fn should_exit_callback
acds_client_config_t config
bool connected
Connection status.
acip_transport_t * transport
Transport used for the initial crypto handshake.
socket_t socket
TCP socket to ACDS server.
acip_transport_t * acip_tcp_transport_create(const char *name, socket_t sockfd, crypto_context_t *crypto_ctx)
Create TCP transport from existing socket.
void acip_transport_destroy(acip_transport_t *transport)
Destroy transport and free all resources.

References acip_tcp_transport_create(), acip_transport_destroy(), ASCIICHAT_OK, acds_client_config_t::callback_data, acds_client_t::config, acds_client_t::connected, ERROR_INVALID_PARAM, ERROR_NETWORK, parallel_connect_config_t::hostname, INVALID_SOCKET_VALUE, log_debug, log_info, NAMED_REGISTER, parallel_connect(), safe_snprintf(), acds_client_config_t::server_address, acds_client_config_t::server_port, SET_ERRNO, acds_client_config_t::should_exit_callback, acds_client_t::socket, socket_close(), acds_client_config_t::timeout_ms, and acds_client_t::transport.

Referenced by client_crypto_init().

◆ acds_client_disconnect()

void acds_client_disconnect ( acds_client_t *  client)

Disconnect from ACDS server.

Closes the connection and cleans up resources.

Parameters
clientClient handle

Definition at line 198 of file acds_client.c.

198 {
199 if (!client) {
200 return;
201 }
202
203 if (client->socket != INVALID_SOCKET_VALUE) {
204 if (client->transport) {
206 client->transport = NULL;
207 }
209 socket_close(client->socket);
211 }
212
213 client->connected = false;
214 log_debug("Disconnected from ACDS server");
215}
void crypto_handshake_destroy(crypto_handshake_context_t *ctx)
Cleanup crypto handshake context with secure memory wiping.
crypto_handshake_context_t handshake_ctx
ACDS crypto handshake state.

References acip_transport_destroy(), acds_client_t::connected, crypto_handshake_destroy(), acds_client_t::handshake_ctx, INVALID_SOCKET_VALUE, log_debug, acds_client_t::socket, socket_close(), and acds_client_t::transport.

Referenced by client_crypto_init().

◆ acds_session_create()

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.

Sends SESSION_CREATE packet and receives SESSION_CREATED response.

Parameters
clientConnected ACDS client
paramsSession creation parameters
resultSession creation result (output)
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 221 of file acds_client.c.

222 {
223 if (!client || !params || !result) {
224 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
225 }
226
227 if (!client->connected) {
228 return SET_ERRNO(ERROR_NETWORK, "Not connected to ACDS server");
229 }
230
231 // Build SESSION_CREATE payload
233 memset(&req, 0, sizeof(req));
234
235 memcpy(req.identity_pubkey, params->identity_pubkey, 32);
236
237 // Sign the request: type || timestamp || capabilities
238 uint64_t timestamp = (uint64_t)time(NULL) * 1000; // Unix ms
239 req.timestamp = timestamp;
240 req.capabilities = params->capabilities;
241 req.max_participants = params->max_participants;
242
243 // Generate Ed25519 signature for identity verification
244 asciichat_error_t sign_result = acds_sign_session_create(params->identity_seckey, timestamp, params->capabilities,
245 params->max_participants, req.signature);
246 if (sign_result != ASCIICHAT_OK) {
247 return SET_ERRNO(ERROR_CRYPTO, "Failed to sign SESSION_CREATE request");
248 }
249
250 req.has_password = params->has_password ? 1 : 0;
251 if (params->has_password) {
252 // Hash password with Argon2id using libsodium
253 // crypto_pwhash_str produces a null-terminated ASCII string
254 if (crypto_pwhash_str((char *)req.password_hash, params->password, strlen(params->password),
255 crypto_pwhash_OPSLIMIT_INTERACTIVE, crypto_pwhash_MEMLIMIT_INTERACTIVE) != 0) {
256 return SET_ERRNO(ERROR_CRYPTO, "Failed to hash password (out of memory)");
257 }
258 } else {
259 memset(req.password_hash, 0, sizeof(req.password_hash));
260 }
261
262 req.reserved_string_len = 0; // Auto-generate
263 if (params->reserved_string && params->reserved_string[0] != '\0') {
264 req.reserved_string_len = strlen(params->reserved_string);
265 // Variable part would follow here (not implemented yet)
266 }
267
268 // Server connection information
269 SAFE_STRNCPY(req.server_address, params->server_address, sizeof(req.server_address));
270 req.server_port = params->server_port;
271
272 // IP disclosure policy
273 // Auto-detection: If password is set, IP will be revealed after verification
274 // If no password, require explicit opt-in via acds_expose_ip
275 req.expose_ip_publicly = params->acds_expose_ip ? 1 : 0;
276
277 // Session type (Direct TCP or WebRTC)
278 req.session_type = params->session_type;
279
280 // Multi-key protocol: set to single-key mode (total_keys=0 for backward compatibility, or =1 for new protocol)
281 // For now, using legacy single-key mode (total_keys=0, key_index=0)
282 req.total_keys = 0; // Legacy mode: server knows this is single-key
283 req.key_index = 0; // Not used in legacy mode
284
285 // Send SESSION_CREATE packet
286 asciichat_error_t send_result = send_packet(client->socket, PACKET_TYPE_ACIP_SESSION_CREATE, &req, sizeof(req));
287 if (send_result != ASCIICHAT_OK) {
288 return send_result;
289 }
290
291 log_debug("Sent SESSION_CREATE request");
292 log_debug("★ ACDS_CLIENT: About to receive SESSION_CREATED on socket %d", client->socket);
293
294 // Receive SESSION_CREATED response
295 packet_type_t resp_type;
296 void *resp_payload = NULL;
297 size_t resp_size = 0;
298
299 int recv_result = receive_packet(client->socket, &resp_type, &resp_payload, &resp_size);
300 log_debug("★ ACDS_CLIENT: receive_packet returned %d", recv_result);
301 if (recv_result < 0) {
302 return SET_ERRNO(ERROR_NETWORK, "Failed to receive SESSION_CREATED response");
303 }
304
305 // Check response type
306 if (resp_type != PACKET_TYPE_ACIP_SESSION_CREATED) {
307 if (resp_type == PACKET_TYPE_ERROR_MESSAGE || resp_type == PACKET_TYPE_ACIP_ERROR) {
308 log_error("Session creation failed: server returned error packet");
309 buffer_pool_free(NULL, resp_payload, resp_size);
310 return SET_ERRNO(ERROR_NETWORK, "Session creation failed");
311 }
312 buffer_pool_free(NULL, resp_payload, resp_size);
313 return SET_ERRNO(ERROR_NETWORK, "Unexpected response type: 0x%02X", resp_type);
314 }
315
316 // Parse SESSION_CREATED response
317 if (resp_size < sizeof(acip_session_created_t)) {
318 buffer_pool_free(NULL, resp_payload, resp_size);
319 return SET_ERRNO(ERROR_NETWORK, "SESSION_CREATED response too small: %zu bytes", resp_size);
320 }
321
322 acip_session_created_t *resp = (acip_session_created_t *)resp_payload;
323
324 // Copy session string (null-terminate)
325 size_t string_len = resp->session_string_len < sizeof(result->session_string) - 1
326 ? resp->session_string_len
327 : sizeof(result->session_string) - 1;
328 memcpy(result->session_string, resp->session_string, string_len);
329 result->session_string[string_len] = '\0';
330
331 memcpy(result->session_id, resp->session_id, 16);
332 result->expires_at = resp->expires_at;
333
334 log_info("Session created: %s (expires at %llu)", result->session_string, (unsigned long long)result->expires_at);
335
336 buffer_pool_free(NULL, resp_payload, resp_size);
337 return ASCIICHAT_OK;
338}
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.
acip_session_created_t
Definition acds.h:269
acip_session_create_t
Definition acds.h:221
void buffer_pool_free(buffer_pool_t *pool, const void *data, size_t size)
Free a buffer back to the pool (lock-free)
unsigned long long uint64_t
Definition common.h:59
@ ERROR_CRYPTO
Definition error_codes.h:96
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
int receive_packet(socket_t sockfd, packet_type_t *type, void **data, size_t *len)
Receive a basic packet without encryption.
Definition packet.c:797
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:784
packet_type_t
Network protocol packet type enumeration.
Definition packet.h:286
@ PACKET_TYPE_ACIP_ERROR
Generic error response (Discovery Server -> Client)
Definition packet.h:509
@ PACKET_TYPE_ACIP_SESSION_CREATE
Create new session (Client -> Discovery Server)
Definition packet.h:407
@ PACKET_TYPE_ACIP_SESSION_CREATED
Session created response (Discovery Server -> Client)
Definition packet.h:409
@ PACKET_TYPE_ERROR_MESSAGE
Error packet with asciichat_error_t code and human-readable message.
Definition packet.h:352
const char * reserved_string
Optional reserved string (NULL = auto-generate)
uint16_t server_port
Server port where clients should connect.
bool acds_expose_ip
Explicitly allow public IP disclosure (–acds-expose-ip opt-in)
uint8_t identity_seckey[64]
Ed25519 secret key (for signing)
uint8_t max_participants
Maximum participants (1-8)
uint8_t identity_pubkey[32]
Ed25519 public key (host identity)
char server_address[64]
Server address where clients should connect.
bool has_password
Password protection enabled.
uint8_t capabilities
Bit 0: video, Bit 1: audio.
char password[128]
Optional password (if has_password)
uint8_t session_type
acds_session_type_t: 0=DIRECT_TCP (default), 1=WEBRTC
uint8_t session_id[16]
Session UUID.
char session_string[49]
Generated session string (null-terminated)
uint64_t expires_at
Expiration timestamp (Unix ms)

References acds_session_create_params_t::acds_expose_ip, acds_sign_session_create(), acip_session_create_t, acip_session_created_t, ASCIICHAT_OK, buffer_pool_free(), acds_session_create_params_t::capabilities, acds_client_t::connected, ERROR_CRYPTO, ERROR_INVALID_PARAM, ERROR_NETWORK, acds_session_create_result_t::expires_at, acds_session_create_params_t::has_password, acds_session_create_params_t::identity_pubkey, acds_session_create_params_t::identity_seckey, log_debug, log_error, log_info, acds_session_create_params_t::max_participants, PACKET_TYPE_ACIP_ERROR, PACKET_TYPE_ACIP_SESSION_CREATE, PACKET_TYPE_ACIP_SESSION_CREATED, PACKET_TYPE_ERROR_MESSAGE, acds_session_create_params_t::password, receive_packet(), acds_session_create_params_t::reserved_string, SAFE_STRNCPY, send_packet(), acds_session_create_params_t::server_address, acds_session_create_params_t::server_port, acds_session_create_result_t::session_id, acds_session_create_result_t::session_string, acds_session_create_params_t::session_type, SET_ERRNO, and acds_client_t::socket.

◆ acds_session_join()

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.

Sends SESSION_JOIN packet and receives SESSION_JOINED response.

Parameters
clientConnected ACDS client
paramsJoin parameters
resultJoin result (output)
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 417 of file acds_client.c.

418 {
419 if (!client || !params || !result) {
420 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
421 }
422
423 if (!client->connected) {
424 return SET_ERRNO(ERROR_NETWORK, "Not connected to ACDS server");
425 }
426
427 // Build SESSION_JOIN payload
429 memset(&req, 0, sizeof(req));
430
431 req.session_string_len = strlen(params->session_string);
432 if (req.session_string_len >= sizeof(req.session_string)) {
433 return SET_ERRNO(ERROR_INVALID_PARAM, "Session string too long");
434 }
435
436 memcpy(req.session_string, params->session_string, req.session_string_len);
437 memcpy(req.identity_pubkey, params->identity_pubkey, 32);
438
439 // Sign the request: type || timestamp || session_string
440 uint64_t timestamp = (uint64_t)time(NULL) * 1000;
441 req.timestamp = timestamp;
442
443 // Generate Ed25519 signature for identity verification
444 asciichat_error_t sign_result =
445 acds_sign_session_join(params->identity_seckey, timestamp, params->session_string, req.signature);
446 if (sign_result != ASCIICHAT_OK) {
447 return SET_ERRNO(ERROR_CRYPTO, "Failed to sign SESSION_JOIN request");
448 }
449
450 req.has_password = params->has_password ? 1 : 0;
451 if (params->has_password) {
452 SAFE_STRNCPY(req.password, params->password, sizeof(req.password));
453 }
454
455 // Send SESSION_JOIN packet
456 asciichat_error_t send_result = send_packet(client->socket, PACKET_TYPE_ACIP_SESSION_JOIN, &req, sizeof(req));
457 if (send_result != ASCIICHAT_OK) {
458 return send_result;
459 }
460
461 log_debug("Sent SESSION_JOIN request for '%s'", params->session_string);
462
463 // Receive SESSION_JOINED response
464 packet_type_t resp_type;
465 void *resp_payload = NULL;
466 size_t resp_size = 0;
467
468 int recv_result = receive_packet(client->socket, &resp_type, &resp_payload, &resp_size);
469 if (recv_result < 0) {
470 return SET_ERRNO(ERROR_NETWORK, "Failed to receive SESSION_JOINED response");
471 }
472
473 if (resp_type != PACKET_TYPE_ACIP_SESSION_JOINED) {
474 buffer_pool_free(NULL, resp_payload, resp_size);
475 return SET_ERRNO(ERROR_NETWORK, "Unexpected response type: 0x%02X", resp_type);
476 }
477
478 if (resp_size < sizeof(acip_session_joined_t)) {
479 buffer_pool_free(NULL, resp_payload, resp_size);
480 return SET_ERRNO(ERROR_NETWORK, "SESSION_JOINED response too small");
481 }
482
483 acip_session_joined_t *resp = (acip_session_joined_t *)resp_payload;
484
485 // Copy result
486 result->success = resp->success != 0;
487 if (result->success) {
488 memcpy(result->participant_id, resp->participant_id, 16);
489 memcpy(result->session_id, resp->session_id, 16);
490 // Server connection information (ONLY revealed after successful authentication)
491 result->session_type = resp->session_type;
492 SAFE_STRNCPY(result->server_address, resp->server_address, sizeof(result->server_address));
493 result->server_port = resp->server_port;
494 log_info("Joined session successfully (participant ID: %02x%02x..., server=%s:%d, type=%s)",
495 result->participant_id[0], result->participant_id[1], result->server_address, result->server_port,
496 result->session_type == SESSION_TYPE_WEBRTC ? "WebRTC" : "DirectTCP");
497 } else {
498 result->error_code = resp->error_code;
499 size_t msg_len = strnlen(resp->error_message, sizeof(resp->error_message));
500 if (msg_len >= sizeof(result->error_message)) {
501 msg_len = sizeof(result->error_message) - 1;
502 }
503 memcpy(result->error_message, resp->error_message, msg_len);
504 result->error_message[msg_len] = '\0';
505 log_warn("Failed to join session: %s (code %d)", result->error_message, result->error_code);
506 }
507
508 buffer_pool_free(NULL, resp_payload, resp_size);
509 return ASCIICHAT_OK;
510}
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.
acip_session_join_t
Definition acds.h:403
acip_session_joined_t
Definition acds.h:474
@ SESSION_TYPE_WEBRTC
WebRTC P2P mesh with STUN/TURN relay.
Definition acds.h:146
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
@ PACKET_TYPE_ACIP_SESSION_JOIN
Join existing session (Client -> Discovery Server)
Definition packet.h:415
@ PACKET_TYPE_ACIP_SESSION_JOINED
Session joined response (Discovery Server -> Client)
Definition packet.h:417
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)
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)

References acds_sign_session_join(), acip_session_join_t, acip_session_joined_t, ASCIICHAT_OK, buffer_pool_free(), acds_client_t::connected, acds_session_join_result_t::error_code, ERROR_CRYPTO, ERROR_INVALID_PARAM, acds_session_join_result_t::error_message, ERROR_NETWORK, acds_session_join_params_t::has_password, acds_session_join_params_t::identity_pubkey, acds_session_join_params_t::identity_seckey, log_debug, log_info, log_warn, PACKET_TYPE_ACIP_SESSION_JOIN, PACKET_TYPE_ACIP_SESSION_JOINED, acds_session_join_result_t::participant_id, acds_session_join_params_t::password, receive_packet(), SAFE_STRNCPY, send_packet(), acds_session_join_result_t::server_address, acds_session_join_result_t::server_port, acds_session_join_result_t::session_id, acds_session_join_params_t::session_string, acds_session_join_result_t::session_type, SESSION_TYPE_WEBRTC, SET_ERRNO, acds_client_t::socket, and acds_session_join_result_t::success.

◆ acds_session_lookup()

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.

Sends SESSION_LOOKUP packet and receives SESSION_INFO response.

Parameters
clientConnected ACDS client
session_stringSession string to look up
resultLookup result (output)
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 340 of file acds_client.c.

341 {
342 if (!client || !session_string || !result) {
343 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
344 }
345
346 if (!client->connected) {
347 return SET_ERRNO(ERROR_NETWORK, "Not connected to ACDS server");
348 }
349
350 // Build SESSION_LOOKUP payload
352 memset(&req, 0, sizeof(req));
353
354 req.session_string_len = strlen(session_string);
355 if (req.session_string_len >= sizeof(req.session_string)) {
356 return SET_ERRNO(ERROR_INVALID_PARAM, "Session string too long");
357 }
358
359 memcpy(req.session_string, session_string, req.session_string_len);
360
361 // Send SESSION_LOOKUP packet
362 asciichat_error_t send_result = send_packet(client->socket, PACKET_TYPE_ACIP_SESSION_LOOKUP, &req, sizeof(req));
363 if (send_result != ASCIICHAT_OK) {
364 return send_result;
365 }
366
367 log_debug("Sent SESSION_LOOKUP request for '%s'", session_string);
368
369 // Receive SESSION_INFO response
370 packet_type_t resp_type;
371 void *resp_payload = NULL;
372 size_t resp_size = 0;
373
374 int recv_result = receive_packet(client->socket, &resp_type, &resp_payload, &resp_size);
375 if (recv_result < 0) {
376 return SET_ERRNO(ERROR_NETWORK, "Failed to receive SESSION_INFO response");
377 }
378
379 if (resp_type != PACKET_TYPE_ACIP_SESSION_INFO) {
380 buffer_pool_free(NULL, resp_payload, resp_size);
381 return SET_ERRNO(ERROR_NETWORK, "Unexpected response type: 0x%02X", resp_type);
382 }
383
384 if (resp_size < sizeof(acip_session_info_t)) {
385 buffer_pool_free(NULL, resp_payload, resp_size);
386 return SET_ERRNO(ERROR_NETWORK, "SESSION_INFO response too small");
387 }
388
389 acip_session_info_t *resp = (acip_session_info_t *)resp_payload;
390
391 // Copy result
392 result->found = resp->found != 0;
393 if (result->found) {
394 memcpy(result->session_id, resp->session_id, 16);
395 memcpy(result->host_pubkey, resp->host_pubkey, 32);
396 result->capabilities = resp->capabilities;
397 result->max_participants = resp->max_participants;
398 result->current_participants = resp->current_participants;
399 result->has_password = resp->has_password != 0;
400 result->created_at = resp->created_at;
401 result->expires_at = resp->expires_at;
402 result->require_server_verify = resp->require_server_verify != 0;
403 result->require_client_verify = resp->require_client_verify != 0;
404
405 log_info("Session found: %s (%d/%d participants, password=%s, policies: server_verify=%d client_verify=%d)",
406 session_string, result->current_participants, result->max_participants,
407 result->has_password ? "required" : "not required", result->require_server_verify,
408 result->require_client_verify);
409 } else {
410 log_info("Session not found: %s", session_string);
411 }
412
413 buffer_pool_free(NULL, resp_payload, resp_size);
414 return ASCIICHAT_OK;
415}
acip_session_lookup_t
Definition acds.h:304
acip_session_info_t
Definition acds.h:355
@ PACKET_TYPE_ACIP_SESSION_INFO
Session info response (Discovery Server -> Client)
Definition packet.h:413
@ PACKET_TYPE_ACIP_SESSION_LOOKUP
Lookup session by string (Client -> Discovery Server)
Definition packet.h:411
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)

References acip_session_info_t, acip_session_lookup_t, ASCIICHAT_OK, buffer_pool_free(), acds_session_lookup_result_t::capabilities, acds_client_t::connected, acds_session_lookup_result_t::created_at, acds_session_lookup_result_t::current_participants, ERROR_INVALID_PARAM, ERROR_NETWORK, ERROR_NOT_SUPPORTED, acds_session_lookup_result_t::expires_at, acds_session_lookup_result_t::found, acds_session_lookup_result_t::has_password, acds_session_lookup_result_t::host_pubkey, log_debug, log_info, acds_session_lookup_result_t::max_participants, PACKET_TYPE_ACIP_SESSION_INFO, PACKET_TYPE_ACIP_SESSION_LOOKUP, receive_packet(), acds_session_lookup_result_t::require_client_verify, acds_session_lookup_result_t::require_server_verify, send_packet(), acds_session_lookup_result_t::session_id, SET_ERRNO, and acds_client_t::socket.

Referenced by client_crypto_init().

◆ acds_sign_session_create()

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.

Computes Ed25519 signature over: type || timestamp || capabilities || max_participants

Parameters
identity_seckeyEd25519 secret key (64 bytes)
timestampUnix milliseconds timestamp
capabilitiesCapabilities bitfield
max_participantsMaximum participants (1-8)
signature_outOutput buffer for signature (64 bytes)
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 516 of file acds_client.c.

517 {
518 if (!identity_seckey || !signature_out) {
519 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL parameter to acds_sign_session_create");
520 }
521
522 // Build message: type (1 byte) || timestamp (8 bytes) || capabilities (1 byte) || max_participants (1 byte)
523 uint8_t message[11];
525
526 // Convert timestamp to network byte order and pack into message
527 uint64_t timestamp_net = HOST_TO_NET_U64(timestamp);
528 memcpy(&message[1], &timestamp_net, sizeof(timestamp_net));
529
530 message[9] = capabilities;
531 message[10] = max_participants;
532
533 // Sign using Ed25519
534 if (crypto_sign_detached(signature_out, NULL, message, sizeof(message), identity_seckey) != 0) {
535 return SET_ERRNO(ERROR_CRYPTO, "Ed25519 signature generation failed");
536 }
537
538 log_debug("Generated SESSION_CREATE signature (timestamp=%llu, caps=%u, max=%u)", (unsigned long long)timestamp,
539 capabilities, max_participants);
540
541 return ASCIICHAT_OK;
542}
#define HOST_TO_NET_U64(val)
Definition endian.h:213
unsigned char uint8_t
Definition common.h:56

References ASCIICHAT_OK, ERROR_CRYPTO, ERROR_INVALID_PARAM, HOST_TO_NET_U64, log_debug, PACKET_TYPE_ACIP_SESSION_CREATE, and SET_ERRNO.

Referenced by acds_session_create().

◆ acds_sign_session_join()

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.

Computes Ed25519 signature over: type || timestamp || session_string

Parameters
identity_seckeyEd25519 secret key (64 bytes)
timestampUnix milliseconds timestamp
session_stringSession string (null-terminated)
signature_outOutput buffer for signature (64 bytes)
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 572 of file acds_client.c.

573 {
574 if (!identity_seckey || !session_string || !signature_out) {
575 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL parameter to acds_sign_session_join");
576 }
577
578 size_t session_len = strlen(session_string);
579 if (session_len > 48) {
580 return SET_ERRNO(ERROR_INVALID_PARAM, "Session string too long (max 48 chars)");
581 }
582
583 // Build message: type (1 byte) || timestamp (8 bytes) || session_string (variable length)
584 uint8_t message[1 + 8 + 48];
586
587 // Convert timestamp to network byte order and pack into message
588 uint64_t timestamp_net = HOST_TO_NET_U64(timestamp);
589 memcpy(&message[1], &timestamp_net, sizeof(timestamp_net));
590
591 // Copy session string (copy exactly session_len bytes, no null terminator in signature)
592 memcpy(&message[9], session_string, session_len);
593
594 size_t message_len = 9 + session_len;
595
596 // Sign using Ed25519
597 if (crypto_sign_detached(signature_out, NULL, message, message_len, identity_seckey) != 0) {
598 return SET_ERRNO(ERROR_CRYPTO, "Ed25519 signature generation failed");
599 }
600
601 log_debug("Generated SESSION_JOIN signature (timestamp=%llu, session='%s')", (unsigned long long)timestamp,
602 session_string);
603
604 return ASCIICHAT_OK;
605}

References ASCIICHAT_OK, ERROR_CRYPTO, ERROR_INVALID_PARAM, HOST_TO_NET_U64, log_debug, PACKET_TYPE_ACIP_SESSION_JOIN, and SET_ERRNO.

Referenced by acds_session_join().

◆ acds_validate_timestamp()

bool acds_validate_timestamp ( uint64_t  timestamp_ms,
uint32_t  window_seconds 
)

Check if timestamp is within acceptable window.

Validates timestamp is recent (within window_seconds of current time) and not in the future.

Parameters
timestamp_msUnix milliseconds timestamp
window_secondsAcceptable age window (e.g., 300 for 5 minutes)
Returns
true if timestamp is valid, false if too old or in the future

Definition at line 641 of file acds_client.c.

641 {
642 uint64_t now_ms = time_ns_to_ms(time_get_realtime_ns());
643 uint64_t window_ms = (uint64_t)window_seconds * 1000;
644
645 if (timestamp_ms > now_ms + 60000) {
646 int64_t skew = (int64_t)timestamp_ms - (int64_t)now_ms;
647 log_warn("Timestamp is in the future: %llu > %llu (skew: %lld ms)", (unsigned long long)timestamp_ms,
648 (unsigned long long)now_ms, (long long)skew);
649 return false;
650 }
651
652 uint64_t min_valid_timestamp = (now_ms >= window_ms) ? (now_ms - window_ms) : 0;
653 if (timestamp_ms < min_valid_timestamp) {
654 int64_t age = (int64_t)now_ms - (int64_t)timestamp_ms;
655 log_warn("Timestamp is too old: %llu < %llu (age: %lld ms, max: %u seconds)", (unsigned long long)timestamp_ms,
656 (unsigned long long)min_valid_timestamp, (long long)age, window_seconds);
657 return false;
658 }
659
660 int64_t age = (int64_t)now_ms - (int64_t)timestamp_ms;
661 log_debug("Timestamp validation passed (age: %lld ms, window: %u seconds)", (long long)age, window_seconds);
662 return true;
663}
uint64_t time_get_realtime_ns(void)
Get current wall-clock (real) time in nanoseconds.
Definition util/time.c:119

References log_debug, log_warn, and time_get_realtime_ns().

◆ acds_verify_session_create()

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.

Verifies Ed25519 signature matches the message fields.

Parameters
identity_pubkeyEd25519 public key (32 bytes)
timestampUnix milliseconds timestamp
capabilitiesCapabilities bitfield
max_participantsMaximum participants
signatureSignature to verify (64 bytes)
Returns
ASCIICHAT_OK if valid, ERROR_CRYPTO_VERIFY_FAILED if invalid

Definition at line 544 of file acds_client.c.

546 {
547 if (!identity_pubkey || !signature) {
548 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL parameter to acds_verify_session_create");
549 }
550
551 // Build message: type (1 byte) || timestamp (8 bytes) || capabilities (1 byte) || max_participants (1 byte)
552 uint8_t message[11];
554
555 // Convert timestamp to network byte order and pack into message
556 uint64_t timestamp_net = HOST_TO_NET_U64(timestamp);
557 memcpy(&message[1], &timestamp_net, sizeof(timestamp_net));
558
559 message[9] = capabilities;
560 message[10] = max_participants;
561
562 // Verify using Ed25519
563 if (crypto_sign_verify_detached(signature, message, sizeof(message), identity_pubkey) != 0) {
564 log_warn("SESSION_CREATE signature verification failed");
565 return SET_ERRNO(ERROR_CRYPTO_VERIFICATION, "Invalid SESSION_CREATE signature");
566 }
567
568 log_debug("SESSION_CREATE signature verified successfully");
569 return ASCIICHAT_OK;
570}
@ ERROR_CRYPTO_VERIFICATION

References ASCIICHAT_OK, ERROR_CRYPTO_VERIFICATION, ERROR_INVALID_PARAM, HOST_TO_NET_U64, log_debug, log_warn, PACKET_TYPE_ACIP_SESSION_CREATE, and SET_ERRNO.

◆ acds_verify_session_join()

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.

Verifies Ed25519 signature matches the message fields.

Parameters
identity_pubkeyEd25519 public key (32 bytes)
timestampUnix milliseconds timestamp
session_stringSession string (null-terminated)
signatureSignature to verify (64 bytes)
Returns
ASCIICHAT_OK if valid, ERROR_CRYPTO_VERIFY_FAILED if invalid

Definition at line 607 of file acds_client.c.

608 {
609 if (!identity_pubkey || !session_string || !signature) {
610 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL parameter to acds_verify_session_join");
611 }
612
613 size_t session_len = strlen(session_string);
614 if (session_len > 48) {
615 return SET_ERRNO(ERROR_INVALID_PARAM, "Session string too long (max 48 chars)");
616 }
617
618 // Build message: type (1 byte) || timestamp (8 bytes) || session_string (variable length)
619 uint8_t message[1 + 8 + 48];
621
622 // Convert timestamp to network byte order and pack into message
623 uint64_t timestamp_net = HOST_TO_NET_U64(timestamp);
624 memcpy(&message[1], &timestamp_net, sizeof(timestamp_net));
625
626 // Copy session string (copy exactly session_len bytes, no null terminator in signature)
627 memcpy(&message[9], session_string, session_len);
628
629 size_t message_len = 9 + session_len;
630
631 // Verify using Ed25519
632 if (crypto_sign_verify_detached(signature, message, message_len, identity_pubkey) != 0) {
633 log_warn("SESSION_JOIN signature verification failed");
634 return SET_ERRNO(ERROR_CRYPTO_VERIFICATION, "Invalid SESSION_JOIN signature");
635 }
636
637 log_debug("SESSION_JOIN signature verified successfully");
638 return ASCIICHAT_OK;
639}

References ASCIICHAT_OK, ERROR_CRYPTO_VERIFICATION, ERROR_INVALID_PARAM, HOST_TO_NET_U64, log_debug, log_warn, PACKET_TYPE_ACIP_SESSION_JOIN, and SET_ERRNO.