30#include <sys/socket.h>
43 memset(config, 0,
sizeof(*config));
54 if (!client || !config) {
58 memset(client, 0,
sizeof(*client));
64 client->
socket = socket(AF_INET, SOCK_STREAM, 0);
77 struct sockaddr_in server_addr;
78 memset(&server_addr, 0,
sizeof(server_addr));
79 server_addr.sin_family = AF_INET;
82 if (inet_pton(AF_INET, config->
server_address, &server_addr.sin_addr) <= 0) {
88 if (connect(client->
socket, (
struct sockaddr *)&server_addr,
sizeof(server_addr)) < 0) {
112 log_debug(
"Disconnected from ACDS server");
121 if (!client || !params || !result) {
131 memset(&req, 0,
sizeof(req));
137 req.timestamp = timestamp;
152 if (crypto_pwhash_str((
char *)req.password_hash, params->
password, strlen(params->
password),
153 crypto_pwhash_OPSLIMIT_INTERACTIVE, crypto_pwhash_MEMLIMIT_INTERACTIVE) != 0) {
157 memset(req.password_hash, 0,
sizeof(req.password_hash));
160 req.reserved_string_len = 0;
184 log_debug(
"Sent SESSION_CREATE request");
188 void *resp_payload = NULL;
189 size_t resp_size = 0;
192 if (recv_result < 0) {
199 log_error(
"Session creation failed: server returned error packet");
216 size_t string_len = resp->session_string_len <
sizeof(result->
session_string) - 1
217 ? resp->session_string_len
222 memcpy(result->
session_id, resp->session_id, 16);
233 if (!client || !session_string || !result) {
243 memset(&req, 0,
sizeof(req));
245 req.session_string_len = strlen(session_string);
246 if (req.session_string_len >=
sizeof(req.session_string)) {
250 memcpy(req.session_string, session_string, req.session_string_len);
258 log_debug(
"Sent SESSION_LOOKUP request for '%s'", session_string);
262 void *resp_payload = NULL;
263 size_t resp_size = 0;
266 if (recv_result < 0) {
283 result->
found = resp->found != 0;
285 memcpy(result->
session_id, resp->session_id, 16);
286 memcpy(result->
host_pubkey, resp->host_pubkey, 32);
296 log_info(
"Session found: %s (%d/%d participants, password=%s, policies: server_verify=%d client_verify=%d)",
301 log_info(
"Session not found: %s", session_string);
310 if (!client || !params || !result) {
320 memset(&req, 0,
sizeof(req));
323 if (req.session_string_len >=
sizeof(req.session_string)) {
327 memcpy(req.session_string, params->
session_string, req.session_string_len);
332 req.timestamp = timestamp;
356 void *resp_payload = NULL;
357 size_t resp_size = 0;
360 if (recv_result < 0) {
377 result->
success = resp->success != 0;
380 memcpy(result->
session_id, resp->session_id, 16);
385 log_info(
"Joined session successfully (participant ID: %02x%02x..., server=%s:%d, type=%s)",
390 size_t msg_len = strnlen(resp->error_message,
sizeof(resp->error_message));
409 if (!identity_seckey || !signature_out) {
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);
427 message[9] = capabilities;
428 message[10] = max_participants;
431 if (crypto_sign_detached(signature_out, NULL, message,
sizeof(message), identity_seckey) != 0) {
435 log_debug(
"Generated SESSION_CREATE signature (timestamp=%llu, caps=%u, max=%u)", (
unsigned long long)timestamp,
436 capabilities, max_participants);
444 if (!identity_pubkey || !signature) {
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);
462 message[9] = capabilities;
463 message[10] = max_participants;
466 if (crypto_sign_verify_detached(signature, message,
sizeof(message), identity_pubkey) != 0) {
467 log_warn(
"SESSION_CREATE signature verification failed");
471 log_debug(
"SESSION_CREATE signature verified successfully");
476 const char *session_string,
uint8_t signature_out[64]) {
477 if (!identity_seckey || !session_string || !signature_out) {
481 size_t session_len = strlen(session_string);
482 if (session_len > 48) {
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);
501 memcpy(&message[9], session_string, session_len);
503 size_t message_len = 9 + session_len;
506 if (crypto_sign_detached(signature_out, NULL, message, message_len, identity_seckey) != 0) {
510 log_debug(
"Generated SESSION_JOIN signature (timestamp=%llu, session='%s')", (
unsigned long long)timestamp,
517 const char *session_string,
const uint8_t signature[64]) {
518 if (!identity_pubkey || !session_string || !signature) {
522 size_t session_len = strlen(session_string);
523 if (session_len > 48) {
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);
542 memcpy(&message[9], session_string, session_len);
544 size_t message_len = 9 + session_len;
547 if (crypto_sign_verify_detached(signature, message, message_len, identity_pubkey) != 0) {
548 log_warn(
"SESSION_JOIN signature verification failed");
552 log_debug(
"SESSION_JOIN signature verified successfully");
559 if (clock_gettime(CLOCK_REALTIME, &ts) != 0) {
568 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);
578 uint64_t min_valid_timestamp = (now_ms >= window_ms) ? (now_ms - window_ms) : 0;
579 if (timestamp_ms < min_valid_timestamp) {
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);
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);
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.
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.
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
#define ACIP_DISCOVERY_DEFAULT_PORT
Discovery server default port.
@ 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)
#define SAFE_STRNCPY(dst, src, size)
unsigned long long uint64_t
#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)
@ ERROR_CRYPTO_VERIFICATION
#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.
int send_packet(socket_t sockfd, packet_type_t type, const void *data, size_t len)
Send a basic packet without encryption.
packet_type_t
Network protocol packet type enumeration.
@ PACKET_TYPE_ACIP_SESSION_INFO
Session info response (Discovery Server -> Client)
@ PACKET_TYPE_ACIP_ERROR
Generic error response (Discovery Server -> Client)
@ PACKET_TYPE_ACIP_SESSION_CREATE
Create new session (Client -> Discovery Server)
@ PACKET_TYPE_ACIP_SESSION_LOOKUP
Lookup session by string (Client -> Discovery Server)
@ PACKET_TYPE_ACIP_SESSION_JOIN
Join existing session (Client -> Discovery Server)
@ PACKET_TYPE_ACIP_SESSION_CREATED
Session created response (Discovery Server -> Client)
@ PACKET_TYPE_ERROR_MESSAGE
Error packet with asciichat_error_t code and human-readable message.
@ PACKET_TYPE_ACIP_SESSION_JOINED
Session joined response (Discovery Server -> Client)
📝 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.
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)
ACDS client connection handle.
acds_client_config_t config
bool connected
Connection status.
socket_t socket
TCP socket to ACDS server.
Session creation request parameters.
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)
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)
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