ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
Network Module

🌐 Core network I/O operations with timeout support More...

Files

file  frame_validator.c
 Frame validation implementation for IMAGE_FRAME packets.
 
file  http_client.c
 🌐 HTTPS client with BearSSL for fetching public keys from GitHub/GitLab with CA validation
 
file  network.c
 🌐 Cross-platform socket I/O with timeout management and connection handling
 
file  callback_timing.c
 WebSocket callback timing instrumentation implementation.
 
file  server.c
 WebSocket server implementation.
 
file  protocol.h
 Server packet processing and protocol implementation.
 
file  protocol.h
 ascii-chat Binary Network Protocol (complete system including ACIP discovery service)
 
file  dns.h
 🌐 DNS resolution utilities
 
file  frame_validator.h
 Frame validation utilities for IMAGE_FRAME packets.
 
file  http_client.h
 Simple HTTPS client for fetching public keys from GitHub/GitLab.
 
file  network.h
 🌐 Core network I/O operations with timeout support
 
file  parallel_connect.h
 Parallel IPv4/IPv6 connection with race-to-connect semantics.
 
file  tailscale.h
 Tailscale host identity helpers.
 
file  update_checker.h
 🔄 Update checker for GitHub releases with DNS connectivity test and cache
 
file  callback_timing.h
 WebSocket callback timing instrumentation.
 
file  internal.h
 Internal WebSocket implementation types shared between transport.c and server.c.
 
file  server.h
 WebSocket server for accepting browser client connections.
 

Data Structures

struct  packet_header_t
 Network packet header structure. More...
 
struct  size_packet_t
 Terminal size update packet. More...
 
struct  client_info_packet_t
 Client information packet structure. More...
 
struct  stream_header_t
 Stream header packet structure. More...
 
struct  client_list_packet_t
 Client list packet structure. More...
 
struct  server_state_packet_t
 Server state packet structure. More...
 
struct  error_packet_t
 Error packet structure carrying error code and textual description. More...
 
struct  remote_log_packet_t
 Remote log packet structure carrying log level and message text. More...
 
struct  auth_failure_packet_t
 Authentication failure packet structure. More...
 
struct  protocol_version_packet_t
 Protocol version negotiation packet structure (Packet Type 1) More...
 
struct  ascii_frame_packet_t
 ASCII frame packet structure (Packet Type 2) More...
 
struct  image_frame_packet_t
 Image frame packet structure (Packet Type 3) More...
 
struct  audio_batch_packet_t
 Audio batch packet structure (Packet Type 28) More...
 
struct  crypto_capabilities_packet_t
 Crypto capabilities packet structure (Packet Type 14) More...
 
struct  crypto_parameters_packet_t
 Crypto parameters packet structure (Packet Type 15) More...
 
struct  terminal_capabilities_packet_t
 Terminal capabilities packet structure (Packet Type 5) More...
 
struct  packet_envelope_t
 Packet envelope containing received packet data. More...
 
struct  update_check_result_t
 Update check result data. More...
 

Macros

#define REMOTE_LOG_FLAG_TRUNCATED   0x0001U
 Remote log packet flag definitions.
 

Enumerations

enum  packet_recv_result_t { PACKET_RECV_SUCCESS = 0 , PACKET_RECV_EOF = -1 , PACKET_RECV_ERROR = -2 , PACKET_RECV_SECURITY_VIOLATION = -3 }
 Packet reception result codes. More...
 
enum  install_method_t { INSTALL_METHOD_HOMEBREW , INSTALL_METHOD_ARCH_AUR , INSTALL_METHOD_GITHUB , INSTALL_METHOD_UNKNOWN }
 Installation method for suggesting upgrade command. More...
 

Functions

bool dns_test_connectivity (const char *hostname)
 Test DNS connectivity by resolving a hostname.
 
asciichat_error_t frame_validate_legacy (size_t len, size_t expected_rgb_size)
 Validate legacy frame format.
 
asciichat_error_t frame_validate_new (void *data, size_t len, bool *out_compressed, uint32_t *out_data_size)
 Validate new frame format with optional compression.
 
asciichat_error_t frame_check_size_overflow (size_t header_size, size_t data_size)
 Check for overflow when adding header to data.
 
void frame_extract_dimensions (const void *data, uint32_t *width, uint32_t *height)
 Extract width and height from frame header.
 
void frame_extract_new_header (const void *data, uint32_t *compressed, uint32_t *data_size)
 Extract compressed flag and data size from new format header.
 
asciichat_error_t update_check_perform (update_check_result_t *result)
 Check for updates from GitHub releases API.
 
asciichat_error_t update_check_load_cache (update_check_result_t *result)
 Load cached update check result.
 
asciichat_error_t update_check_save_cache (const update_check_result_t *result)
 Save update check result to cache.
 
bool update_check_is_cache_fresh (const update_check_result_t *result)
 Check if cache is fresh (< 7 days old)
 
install_method_t update_check_detect_install_method (void)
 Detect installation method.
 
void update_check_get_upgrade_suggestion (install_method_t method, const char *latest_version, char *buffer, size_t buffer_size)
 Get upgrade suggestion string.
 
void update_check_format_notification (const update_check_result_t *result, char *buffer, size_t buffer_size)
 Format update notification message.
 
asciichat_error_t update_check_startup (update_check_result_t *result)
 Perform startup update check with caching.
 

HTTPS Client

char * https_get (const char *hostname, const char *path)
 Perform HTTPS GET request.
 

Socket I/O Operations

ssize_t send_with_timeout (socket_t sockfd, const void *data, size_t len, uint64_t timeout_ns)
 Send data with timeout using chunked transmission.
 
ssize_t recv_with_timeout (socket_t sockfd, void *buf, size_t len, uint64_t timeout_ns)
 Receive data with timeout.
 
int accept_with_timeout (socket_t listenfd, struct sockaddr *addr, socklen_t *addrlen, uint64_t timeout_ns)
 Accept connection with timeout.
 
bool connect_with_timeout (socket_t sockfd, const struct sockaddr *addr, socklen_t addrlen, int timeout_seconds)
 Connect to server with timeout.
 

Socket Configuration Functions

asciichat_error_t set_socket_timeout (socket_t sockfd, uint64_t timeout_ns)
 Set socket timeout for send/receive operations.
 
asciichat_error_t set_socket_keepalive (socket_t sockfd)
 Enable TCP keepalive on socket.
 
asciichat_error_t set_socket_nonblocking (socket_t sockfd)
 Set socket to non-blocking mode.
 
asciichat_error_t socket_configure_buffers (socket_t sockfd)
 Configure socket buffers and TCP_NODELAY for optimal performance.
 

Error Reporting Functions

const char * network_error_string ()
 Get human-readable error string for network errors.
 

Basic Packet I/O Functions

asciichat_error_t packet_send (socket_t sockfd, packet_type_t type, const void *data, size_t len)
 Send a packet with header and CRC32 checksum.
 
asciichat_error_t packet_receive (socket_t sockfd, packet_type_t *type, void **data, size_t *len)
 Receive a packet with header validation and CRC32 checking.
 

Secure Packet I/O Functions

asciichat_error_t send_packet_secure (socket_t sockfd, packet_type_t type, const void *data, size_t len, crypto_context_t *crypto_ctx)
 Send a packet with encryption and compression support.
 
packet_recv_result_t receive_packet_secure (socket_t sockfd, void *crypto_ctx, bool enforce_encryption, packet_envelope_t *envelope)
 Receive a packet with decryption and decompression support.
 
asciichat_error_t packet_decrypt_envelope (packet_envelope_t *envelope, void *crypto_ctx)
 Decrypt a PACKET_TYPE_ENCRYPTED envelope and extract inner packet.
 
packet_recv_result_t receive_packet_secure_with_timeout (socket_t sockfd, void *crypto_ctx, bool enforce_encryption, packet_envelope_t *envelope, uint64_t timeout_ns)
 

Legacy Packet I/O Functions

These functions provide basic packet I/O without encryption support. Use send_packet_secure() and receive_packet_secure() for encryption support.

int send_packet (socket_t sockfd, packet_type_t type, const void *data, size_t len)
 Send a basic packet without encryption.
 
int receive_packet (socket_t sockfd, packet_type_t *type, void **data, size_t *len)
 Receive a basic packet without encryption.
 

Protocol Packet Functions

Convenience functions for sending specific protocol packets. These functions construct the appropriate packet structure and send it over the socket.

int send_ping_packet (socket_t sockfd)
 Send a ping packet (keepalive)
 
int send_pong_packet (socket_t sockfd)
 Send a pong packet (keepalive response)
 
int send_protocol_version_packet (socket_t sockfd, const protocol_version_packet_t *version)
 Send protocol version negotiation packet.
 
int send_crypto_capabilities_packet (socket_t sockfd, const crypto_capabilities_packet_t *caps)
 Send crypto capabilities packet.
 
int send_crypto_parameters_packet (socket_t sockfd, const crypto_parameters_packet_t *params)
 Send crypto parameters packet.
 
asciichat_error_t av_send_audio_opus_batch (socket_t sockfd, const uint8_t *opus_data, size_t opus_size, const uint16_t *frame_sizes, int sample_rate, int frame_duration, int frame_count, crypto_context_t *crypto_ctx)
 Send Opus-encoded audio batch packet with encryption support.
 
asciichat_error_t send_image_frame_packet (socket_t sockfd, const void *image_data, uint16_t width, uint16_t height, uint8_t format)
 Send image frame packet.
 
asciichat_error_t packet_send_error (socket_t sockfd, const crypto_context_t *crypto_ctx, asciichat_error_t error_code, const char *message)
 Send an error packet with optional encryption context.
 
asciichat_error_t packet_parse_error_message (const void *data, size_t len, asciichat_error_t *out_error_code, char *message_buffer, size_t message_buffer_size, size_t *out_message_length)
 Parse an error packet payload into components.
 
asciichat_error_t packet_send_remote_log (socket_t sockfd, const crypto_context_t *crypto_ctx, log_level_t level, remote_log_direction_t direction, uint16_t flags, const char *message)
 Send a remote log packet with optional encryption context.
 
asciichat_error_t packet_parse_remote_log (const void *data, size_t len, log_level_t *out_level, remote_log_direction_t *out_direction, uint16_t *out_flags, char *message_buffer, size_t message_buffer_size, size_t *out_message_length)
 Parse a remote log packet payload into components.
 

Network Timeout Constants

Timeout values tuned for real-time video streaming. All timeouts are specified in seconds.

#define CONNECT_TIMEOUT   3
 Connection timeout in seconds (3 seconds)
 
#define SEND_TIMEOUT   1
 Send timeout in seconds (5 seconds)
 
#define RECV_TIMEOUT   1
 Receive timeout in seconds (1 second)
 
#define ACCEPT_TIMEOUT   1
 Accept timeout in seconds (1 second)
 

Test Environment Detection

#define network_is_test_environment()   ((int)is_test_environment())
 Check if we're in a test environment.
 

Socket Keepalive Settings

Keepalive settings to detect dead connections. TCP keepalive probes are sent when connection is idle to detect broken connections.

#define KEEPALIVE_IDLE   60
 Keepalive idle time in seconds (60 seconds)
 
#define KEEPALIVE_INTERVAL   10
 Keepalive interval in seconds (10 seconds)
 
#define KEEPALIVE_COUNT   8
 Keepalive probe count (8 probes)
 

Network Protocol Constants

#define MAX_ERROR_MESSAGE_LENGTH   512
 Maximum error message length (512 bytes)
 
#define MAX_REMOTE_LOG_MESSAGE_LENGTH   512
 Maximum remote log message length (512 bytes)
 

Protocol Negotiation Constants

#define FEATURE_RLE_ENCODING   0x01
 Feature flags for protocol_version_packet_t.
 
#define FEATURE_DELTA_FRAMES   0x02
 Delta frame encoding (future)
 

Client Capability Flags

Bitmask flags for client capabilities in multi-user protocol.

#define CLIENT_CAP_VIDEO   0x01
 Client can send/receive video.
 
#define CLIENT_CAP_AUDIO   0x02
 Client can send/receive audio.
 
#define CLIENT_CAP_COLOR   0x04
 Client supports color rendering.
 
#define CLIENT_CAP_STRETCH   0x08
 Client can stretch frames to fill terminal.
 

Stream Type Flags

Bitmask flags for stream types in multi-user protocol.

#define STREAM_TYPE_VIDEO   0x01
 Video stream.
 
#define STREAM_TYPE_AUDIO   0x02
 Audio stream.
 

Crypto Algorithm Constants

Algorithm identifiers for key exchange, authentication, and encryption. Used in crypto handshake packet negotiation.

#define KEX_ALGO_X25519   0x01
 X25519 key exchange (Curve25519)
 
#define AUTH_ALGO_ED25519   0x01
 Ed25519 authentication (Edwards-curve signatures)
 
#define AUTH_ALGO_NONE   0x00
 No authentication (plaintext mode)
 
#define CIPHER_ALGO_XSALSA20_POLY1305   0x01
 XSalsa20-Poly1305 authenticated encryption.
 
#define CIPHER_ALGO_NONE   0x00
 No encryption (plaintext mode)
 

Crypto Mode Bitmasks

2-bit bitmask for encryption and authentication modes

These constants define all valid combinations of encryption and authentication in the crypto protocol. The supports_encryption field in protocol_version_packet_t uses these values to negotiate capabilities:

  • Bit 0 (0x01): ACIP_CRYPTO_ENCRYPT - payload encryption enabled
  • Bit 1 (0x02): ACIP_CRYPTO_AUTH - identity authentication enabled
#define ACIP_CRYPTO_NONE   0x00
 No encryption, no authentication (plaintext only)
 
#define ACIP_CRYPTO_ENCRYPT   0x01
 Encryption only (no identity verification)
 
#define ACIP_CRYPTO_AUTH   0x02
 Authentication only (no payload encryption)
 
#define ACIP_CRYPTO_FULL   0x03
 Full crypto: encryption + authentication.
 
#define ACIP_CRYPTO_HAS_ENCRYPT(mode)   (((mode) & ACIP_CRYPTO_ENCRYPT) != 0)
 Check if crypto mode includes payload encryption.
 
#define ACIP_CRYPTO_HAS_AUTH(mode)   (((mode) & ACIP_CRYPTO_AUTH) != 0)
 Check if crypto mode includes authentication.
 

Detailed Description

🌐 Core network I/O operations with timeout support

Provides DNS resolution and connectivity testing utilities.

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
February 2026

This module provides basic HTTPS GET functionality using BearSSL for TLS. It's designed specifically for fetching SSH/GPG public keys from GitHub and GitLab.

Note
TLS library: Uses BearSSL for TLS connections. BearSSL is a minimal TLS implementation with system CA certificate support.
Security: Uses system CA certificates for trust validation (20-year longevity). Validates server certificates against system trust store.
Key fetching: Fetches keys from GitHub/GitLab endpoints:
Key fetching functions: fetch_github_ssh_keys, fetch_gitlab_ssh_keys, fetch_github_gpg_keys, and fetch_gitlab_gpg_keys are in crypto/keys/https_keys.h. They properly belong in the keys module.
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
October 2025

This module provides fundamental network I/O operations including socket management, timeout handling, and basic send/receive operations. All operations support configurable timeouts to prevent indefinite blocking, critical for real-time video streaming applications.

CORE RESPONSIBILITIES:

  1. Socket I/O operations with timeout support
  2. Connection management (connect, accept) with timeouts
  3. Socket configuration (keepalive, non-blocking, timeouts)
  4. Chunked transmission for large data transfers
  5. Error reporting and diagnostics

ARCHITECTURAL OVERVIEW:

TIMEOUT SYSTEM:

SOCKET CONFIGURATION:

ERROR HANDLING:

INTEGRATION WITH OTHER MODULES:

PERFORMANCE CHARACTERISTICS:

Note
Timeout values are tuned for real-time video streaming. Adjust CONNECT_TIMEOUT, SEND_TIMEOUT, and RECV_TIMEOUT if needed for different network conditions.
Chunked transmission is used automatically for large data to prevent timeouts and enable progress tracking.
Warning
Timeout values that are too short may cause false positives on slow networks. Values that are too long may delay error detection unnecessarily.
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025
Version
2.0 (Post-Modularization)

This module provides comprehensive packet protocol implementation including packet verification, CRC validation, protocol compliance checking, encryption support, and compression integration. It serves as the core network protocol layer for ascii-chat's communication system.

CORE RESPONSIBILITIES:

  1. Packet header validation and CRC32 checksum verification
  2. Secure packet transmission with encryption support
  3. Secure packet reception with decryption and validation
  4. Protocol compliance checking and error handling
  5. Integration with cryptographic and compression subsystems

ARCHITECTURAL OVERVIEW:

PACKET STRUCTURE:

ENCRYPTION INTEGRATION:

COMPRESSION INTEGRATION:

PROTOCOL VALIDATION:

INTEGRATION WITH OTHER MODULES:

PERFORMANCE CHARACTERISTICS:

Note
All packet functions handle encryption automatically when crypto context is provided. Plaintext packets are used for handshake.
Compression is automatically applied to large packets (frames, audio batches) based on size thresholds.
Warning
Packet buffers are allocated by receive functions and must be freed by caller using the allocated_buffer pointer.
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025
Version
2.0 (Post-Modularization)

Provides automatic and manual update checking against GitHub releases API.

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
February 2026

Macro Definition Documentation

◆ ACCEPT_TIMEOUT

#define ACCEPT_TIMEOUT   1

#include <network.h>

Accept timeout in seconds (1 second)

Maximum time to wait for incoming connections. Balanced between responsiveness and CPU usage. Status screen updates independently in its own thread at target FPS.

Definition at line 142 of file network/network.h.

◆ ACIP_CRYPTO_AUTH

#define ACIP_CRYPTO_AUTH   0x02

#include <packet.h>

Authentication only (no payload encryption)

Definition at line 1084 of file packet.h.

◆ ACIP_CRYPTO_ENCRYPT

#define ACIP_CRYPTO_ENCRYPT   0x01

#include <packet.h>

Encryption only (no identity verification)

Definition at line 1083 of file packet.h.

◆ ACIP_CRYPTO_FULL

#define ACIP_CRYPTO_FULL   0x03

#include <packet.h>

Full crypto: encryption + authentication.

Definition at line 1085 of file packet.h.

◆ ACIP_CRYPTO_HAS_AUTH

#define ACIP_CRYPTO_HAS_AUTH (   mode)    (((mode) & ACIP_CRYPTO_AUTH) != 0)

#include <packet.h>

Check if crypto mode includes authentication.

Parameters
modeCrypto mode bitmask (ACIP_CRYPTO_*)
Returns
true if authentication is enabled, false otherwise

Definition at line 1099 of file packet.h.

◆ ACIP_CRYPTO_HAS_ENCRYPT

#define ACIP_CRYPTO_HAS_ENCRYPT (   mode)    (((mode) & ACIP_CRYPTO_ENCRYPT) != 0)

#include <packet.h>

Check if crypto mode includes payload encryption.

Parameters
modeCrypto mode bitmask (ACIP_CRYPTO_*)
Returns
true if payload encryption is enabled, false otherwise

Definition at line 1092 of file packet.h.

◆ ACIP_CRYPTO_NONE

#define ACIP_CRYPTO_NONE   0x00

#include <packet.h>

No encryption, no authentication (plaintext only)

Definition at line 1082 of file packet.h.

◆ AUTH_ALGO_ED25519

#define AUTH_ALGO_ED25519   0x01

#include <packet.h>

Ed25519 authentication (Edwards-curve signatures)

Definition at line 1065 of file packet.h.

◆ AUTH_ALGO_NONE

#define AUTH_ALGO_NONE   0x00

#include <packet.h>

No authentication (plaintext mode)

Definition at line 1066 of file packet.h.

◆ CIPHER_ALGO_NONE

#define CIPHER_ALGO_NONE   0x00

#include <packet.h>

No encryption (plaintext mode)

Definition at line 1068 of file packet.h.

◆ CIPHER_ALGO_XSALSA20_POLY1305

#define CIPHER_ALGO_XSALSA20_POLY1305   0x01

#include <packet.h>

XSalsa20-Poly1305 authenticated encryption.

Definition at line 1067 of file packet.h.

◆ CLIENT_CAP_AUDIO

#define CLIENT_CAP_AUDIO   0x02

#include <packet.h>

Client can send/receive audio.

Definition at line 924 of file packet.h.

◆ CLIENT_CAP_COLOR

#define CLIENT_CAP_COLOR   0x04

#include <packet.h>

Client supports color rendering.

Definition at line 925 of file packet.h.

◆ CLIENT_CAP_STRETCH

#define CLIENT_CAP_STRETCH   0x08

#include <packet.h>

Client can stretch frames to fill terminal.

Definition at line 926 of file packet.h.

◆ CLIENT_CAP_VIDEO

#define CLIENT_CAP_VIDEO   0x01

#include <packet.h>

Client can send/receive video.

Definition at line 923 of file packet.h.

◆ CONNECT_TIMEOUT

#define CONNECT_TIMEOUT   3

#include <network.h>

Connection timeout in seconds (3 seconds)

Maximum time to wait for connection establishment. Reduced from default for faster connection attempts and quicker failure detection.

Definition at line 98 of file network/network.h.

◆ FEATURE_DELTA_FRAMES

#define FEATURE_DELTA_FRAMES   0x02

#include <packet.h>

Delta frame encoding (future)

Definition at line 803 of file packet.h.

◆ FEATURE_RLE_ENCODING

#define FEATURE_RLE_ENCODING   0x01

#include <packet.h>

Feature flags for protocol_version_packet_t.

Bitmask values for optional protocol features. Used in protocol negotiation to enable advanced capabilities.

Run-length encoding support

Definition at line 802 of file packet.h.

◆ KEEPALIVE_COUNT

#define KEEPALIVE_COUNT   8

#include <network.h>

Keepalive probe count (8 probes)

Number of keepalive probes to send before considering connection dead.

Definition at line 201 of file network/network.h.

◆ KEEPALIVE_IDLE

#define KEEPALIVE_IDLE   60

#include <network.h>

Keepalive idle time in seconds (60 seconds)

Time to wait before sending first keepalive probe after connection becomes idle.

Definition at line 183 of file network/network.h.

◆ KEEPALIVE_INTERVAL

#define KEEPALIVE_INTERVAL   10

#include <network.h>

Keepalive interval in seconds (10 seconds)

Interval between subsequent keepalive probes.

Definition at line 192 of file network/network.h.

◆ KEX_ALGO_X25519

#define KEX_ALGO_X25519   0x01

#include <packet.h>

X25519 key exchange (Curve25519)

Definition at line 1064 of file packet.h.

◆ MAX_ERROR_MESSAGE_LENGTH

#define MAX_ERROR_MESSAGE_LENGTH   512

#include <packet.h>

Maximum error message length (512 bytes)

Error packets include a message payload. This defines the maximum number of bytes allowed in the message portion to prevent excessive allocations and potential abuse.

Definition at line 124 of file packet.h.

◆ MAX_REMOTE_LOG_MESSAGE_LENGTH

#define MAX_REMOTE_LOG_MESSAGE_LENGTH   512

#include <packet.h>

Maximum remote log message length (512 bytes)

Remote logging packets mirror the error packet structure but are intended for diagnostic log forwarding between client and server. Limiting the payload size keeps allocations predictable and prevents log flooding over the network.

Definition at line 134 of file packet.h.

◆ network_is_test_environment

#define network_is_test_environment ( )    ((int)is_test_environment())

#include <network.h>

Check if we're in a test environment.

Compatibility macro that calls is_test_environment() from tests/test_env.h. Used to adjust timeouts for faster test execution.

Returns
1 if test environment, 0 otherwise

Definition at line 162 of file network/network.h.

◆ RECV_TIMEOUT

#define RECV_TIMEOUT   1

#include <network.h>

Receive timeout in seconds (1 second)

Reduced to 1 second to allow snapshot mode to check elapsed time frequently and exit cleanly when snapshot_delay expires. With the longer 30-second timeout, clients in snapshot mode would block waiting for data and miss their snapshot window. The 1-second timeout balances responsiveness with network latency tolerance.

Definition at line 127 of file network/network.h.

◆ REMOTE_LOG_FLAG_TRUNCATED

#define REMOTE_LOG_FLAG_TRUNCATED   0x0001U

#include <packet.h>

Remote log packet flag definitions.

Message payload was truncated to fit the maximum length

Definition at line 736 of file packet.h.

◆ SEND_TIMEOUT

#define SEND_TIMEOUT   1

#include <network.h>

Send timeout in seconds (5 seconds)

Maximum time to wait for data transmission. Video frames need timely delivery to maintain real-time performance.

Definition at line 111 of file network/network.h.

◆ STREAM_TYPE_AUDIO

#define STREAM_TYPE_AUDIO   0x02

#include <packet.h>

Audio stream.

Definition at line 938 of file packet.h.

◆ STREAM_TYPE_VIDEO

#define STREAM_TYPE_VIDEO   0x01

#include <packet.h>

Video stream.

Definition at line 937 of file packet.h.

Enumeration Type Documentation

◆ install_method_t

#include <update_checker.h>

Installation method for suggesting upgrade command.

Enumerator
INSTALL_METHOD_HOMEBREW 

Installed via Homebrew.

INSTALL_METHOD_ARCH_AUR 

Installed via Arch AUR (paru/yay)

INSTALL_METHOD_GITHUB 

Manual install from GitHub releases.

INSTALL_METHOD_UNKNOWN 

Unknown installation method.

Definition at line 44 of file update_checker.h.

44 {
install_method_t
Installation method for suggesting upgrade command.
@ INSTALL_METHOD_ARCH_AUR
Installed via Arch AUR (paru/yay)
@ INSTALL_METHOD_UNKNOWN
Unknown installation method.
@ INSTALL_METHOD_GITHUB
Manual install from GitHub releases.
@ INSTALL_METHOD_HOMEBREW
Installed via Homebrew.

◆ packet_recv_result_t

#include <packet.h>

Packet reception result codes.

Result codes for packet reception operations. Negative values indicate errors, zero indicates success.

Enumerator
PACKET_RECV_SUCCESS 

Packet received successfully.

PACKET_RECV_EOF 

Connection closed (EOF)

PACKET_RECV_ERROR 

Network error occurred.

PACKET_RECV_SECURITY_VIOLATION 

Encryption policy violation (e.g., unencrypted packet when encryption required)

Definition at line 1152 of file packet.h.

1152 {
1156 PACKET_RECV_EOF = -1,
1158 PACKET_RECV_ERROR = -2,
packet_recv_result_t
Packet reception result codes.
Definition packet.h:1152
@ PACKET_RECV_ERROR
Network error occurred.
Definition packet.h:1158
@ PACKET_RECV_EOF
Connection closed (EOF)
Definition packet.h:1156
@ PACKET_RECV_SECURITY_VIOLATION
Encryption policy violation (e.g., unencrypted packet when encryption required)
Definition packet.h:1160
@ PACKET_RECV_SUCCESS
Packet received successfully.
Definition packet.h:1154

Function Documentation

◆ accept_with_timeout()

int accept_with_timeout ( socket_t  listenfd,
struct sockaddr *  addr,
socklen_t *  addrlen,
uint64_t  timeout_ns 
)

#include <network.h>

Accept connection with timeout.

Parameters
listenfdListening socket file descriptor
addrOutput: Client address structure (can be NULL)
addrlenInput/output: Length of address structure
timeout_nsTimeout in nanoseconds
Returns
Client socket file descriptor on success, -1 on error

Accepts incoming connection with timeout support. Waits up to timeout_ns for incoming connection.

Note
If addr is NULL, client address information is not returned.
addrlen must point to initialized value containing size of addr buffer. On return, contains actual size of address.
Parameters
listenfdListening socket
addrClient address structure
addrlenLength of address structure
timeout_secondsTimeout in seconds
Returns
Client socket, or -1 on error

Definition at line 266 of file network/network.c.

266 {
267 // Set up poll for accept timeout (in nanoseconds)
268 struct pollfd pfd;
269 pfd.fd = listenfd;
270 pfd.events = POLLIN;
271 pfd.revents = 0;
272
273 int result = socket_poll(&pfd, 1, (int64_t)timeout_ns);
274
275 if (result <= 0) {
276 if (result == 0) {
277 // Timeout is expected behavior for server waiting for connections
282 return -1;
283 }
284
285 if (network_handle_select_error(result)) {
286 return -1; // Don't retry for accept
287 }
288 return -1;
289 }
290
291 // Check if socket is ready
292 if (!(pfd.revents & POLLIN)) {
297 return -1;
298 }
299
300 // Check if socket is still valid before attempting accept
301 if (listenfd == INVALID_SOCKET_VALUE) {
302 SET_ERRNO(ERROR_NETWORK, "accept_with_timeout: listening socket is closed");
303 return -1;
304 }
305
306 socket_t accept_result = socket_accept(listenfd, addr, addrlen, "connection");
307
308 if (accept_result == INVALID_SOCKET_VALUE) {
309 // Check if this is a socket closed error (common during shutdown)
311
313 // During shutdown, don't log this as an error since it's expected behavior
317 } else {
318 SET_ERRNO_SYS(ERROR_NETWORK_BIND, "accept_with_timeout accept failed: %s", socket_get_error_string());
319 }
320 return -1;
321 }
322
323 return (int)accept_result;
324}
asciichat_error_t error_code
#define SET_ERRNO_SYS(code, context_msg,...)
Set error code with custom message and system error context, returning the error code.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
__thread asciichat_error_t asciichat_errno
Thread-local current error code.
__thread asciichat_error_context_t asciichat_errno_context
Thread-local error context storage.
@ ERROR_NETWORK_BIND
Definition error_codes.h:78
@ ERROR_NETWORK
Definition error_codes.h:77
@ ERROR_NETWORK_TIMEOUT
Definition error_codes.h:80
#define ETIMEDOUT
int socket_poll(struct pollfd *fds, nfds_t nfds, int64_t timeout_ns)
Poll sockets for events (multiplexed I/O)
#define INVALID_SOCKET_VALUE
Invalid socket value (POSIX: -1)
Definition socket.h:278
bool socket_is_invalid_socket_error(int error_code)
Check if error code indicates a closed/invalid socket.
int socket_get_last_error(void)
Get last socket error code.
const char * socket_get_error_string(void)
Get last socket error as string.
socket_t socket_accept(socket_t sock, struct sockaddr *addr, socklen_t *addrlen, const char *name)
Accept an incoming connection.
int socket_t
int system_errno
System errno value (if applicable, 0 otherwise)
bool has_system_error
True if system_errno is valid.
asciichat_error_t code
Error code (asciichat_error_t enum value)

References asciichat_errno, asciichat_errno_context, asciichat_error_context_t::code, error_code, ERROR_NETWORK, ERROR_NETWORK_BIND, ERROR_NETWORK_TIMEOUT, ETIMEDOUT, asciichat_error_context_t::has_system_error, INVALID_SOCKET_VALUE, SET_ERRNO, SET_ERRNO_SYS, socket_accept(), socket_get_error_string(), socket_get_last_error(), socket_is_invalid_socket_error(), socket_poll(), and asciichat_error_context_t::system_errno.

◆ av_send_audio_opus_batch()

asciichat_error_t av_send_audio_opus_batch ( socket_t  sockfd,
const uint8_t *  opus_data,
size_t  opus_size,
const uint16_t *  frame_sizes,
int  sample_rate,
int  frame_duration,
int  frame_count,
crypto_context_t *  crypto_ctx 
)

#include <packet.h>

Send Opus-encoded audio batch packet with encryption support.

Parameters
sockfdSocket file descriptor
opus_dataOpus-encoded audio data buffer
opus_sizeSize of Opus-encoded data in bytes
frame_sizesArray of frame sizes (one per Opus frame)
sample_rateSample rate in Hz (e.g., 48000)
frame_durationFrame duration in milliseconds (e.g., 20)
frame_countNumber of Opus frames in batch
crypto_ctxCryptographic context for encryption (NULL for plaintext)
Returns
ASCIICHAT_OK on success, error code on failure

Sends PACKET_TYPE_AUDIO_OPUS_BATCH with multiple Opus-encoded frames. Opus provides better compression than raw audio (30-100 bytes per 20ms frame).

Note
Opus packets are NOT compressed again (already compressed by Opus codec).
Encryption is applied automatically when crypto_ctx is provided.

Definition at line 1175 of file packet.c.

1177 {
1178 if (!opus_data || opus_size == 0 || !frame_sizes || sample_rate <= 0 || frame_duration <= 0 || frame_count <= 0) {
1180 "Invalid Opus batch parameters: opus_data=%p, opus_size=%zu, frame_sizes=%p, sample_rate=%d, "
1181 "frame_duration=%d, frame_count=%d",
1182 (const void *)opus_data, opus_size, (const void *)frame_sizes, sample_rate, frame_duration,
1183 frame_count);
1184 }
1185
1186 // Validate frame_count to prevent integer overflow in size calculations
1187 // Max reasonable frames per batch: ~1 second of 10ms frames = 100 frames
1188 if (frame_count > 1000) {
1189 return SET_ERRNO(ERROR_INVALID_PARAM, "Too many Opus frames: %d (max 1000)", frame_count);
1190 }
1191
1192 // Allocate buffer for header + frame sizes + encoded data
1193 size_t header_size = 16; // sample_rate (4), frame_duration (4), frame_count (4), reserved (4)
1194 size_t frame_sizes_bytes = (size_t)frame_count * sizeof(uint16_t);
1195 size_t total_size = header_size + frame_sizes_bytes + opus_size;
1196 void *packet_data = buffer_pool_alloc(NULL, total_size);
1197 if (!packet_data) {
1198 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate buffer for Opus batch packet: %zu bytes", total_size);
1199 }
1200
1201 // Write header (network byte order for cross-platform compatibility)
1202 uint8_t *buf = (uint8_t *)packet_data;
1203 uint32_t sr = HOST_TO_NET_U32((uint32_t)sample_rate);
1204 uint32_t fd = HOST_TO_NET_U32((uint32_t)frame_duration);
1205 uint32_t fc = HOST_TO_NET_U32((uint32_t)frame_count);
1206 memcpy(buf, &sr, 4);
1207 memcpy(buf + 4, &fd, 4);
1208 memcpy(buf + 8, &fc, 4);
1209 memset(buf + 12, 0, 4); // Reserved
1210
1211 // Write frame sizes array (convert each to network byte order)
1212 uint16_t *frame_sizes_out = (uint16_t *)(buf + header_size);
1213 for (int i = 0; i < frame_count; i++) {
1214 frame_sizes_out[i] = HOST_TO_NET_U16(frame_sizes[i]);
1215 }
1216
1217 // Copy Opus data
1218 memcpy(buf + header_size + frame_sizes_bytes, opus_data, opus_size);
1219
1220 // Send packet (with encryption support)
1221 asciichat_error_t result =
1222 send_packet_secure(sockfd, PACKET_TYPE_AUDIO_OPUS_BATCH, packet_data, total_size, crypto_ctx);
1223
1224 // Clean up
1225 buffer_pool_free(NULL, packet_data, total_size);
1226
1227 return result;
1228}
#define HOST_TO_NET_U16(val)
Definition endian.h:96
#define HOST_TO_NET_U32(val)
Definition endian.h:66
void buffer_pool_free(buffer_pool_t *pool, const void *data, size_t size)
Free a buffer back to the pool (lock-free)
void * buffer_pool_alloc(buffer_pool_t *pool, size_t size)
Allocate a buffer from the pool (lock-free fast path)
unsigned short uint16_t
Definition common.h:57
unsigned int uint32_t
Definition common.h:58
unsigned char uint8_t
Definition common.h:56
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_MEMORY
Definition error_codes.h:56
@ ERROR_INVALID_PARAM
asciichat_error_t send_packet_secure(socket_t sockfd, packet_type_t type, const void *data, size_t len, crypto_context_t *crypto_ctx)
Send a packet with encryption and compression support.
Definition packet.c:434
@ PACKET_TYPE_AUDIO_OPUS_BATCH
Batched Opus-encoded audio frames.
Definition packet.h:374

References buffer_pool_alloc(), buffer_pool_free(), ERROR_INVALID_PARAM, ERROR_MEMORY, HOST_TO_NET_U16, HOST_TO_NET_U32, PACKET_TYPE_AUDIO_OPUS_BATCH, send_packet_secure(), and SET_ERRNO.

◆ connect_with_timeout()

bool connect_with_timeout ( socket_t  sockfd,
const struct sockaddr *  addr,
socklen_t  addrlen,
int  timeout_seconds 
)

#include <network.h>

Connect to server with timeout.

Parameters
sockfdSocket file descriptor
addrServer address structure
addrlenAddress structure length
timeout_secondsTimeout in seconds
Returns
true on success, false on failure

Establishes connection to server with timeout support. Waits up to timeout_seconds for connection to complete.

Note
Uses platform-specific connection timeout mechanism (select/poll).
Returns false on timeout or connection failure. Use network_error_string() to get human-readable error description.

Connect to server with timeout.

Parameters
sockfdSocket file descriptor
addrAddress to connect to
addrlenAddress length
timeout_secondsTimeout in seconds
Returns
true on success, false on failure

Definition at line 457 of file network/network.c.

457 {
458 if (sockfd == INVALID_SOCKET_VALUE) {
459 errno = EBADF;
460 return false;
461 }
462
463 // Set socket to non-blocking for timeout control
464 if (set_socket_nonblocking(sockfd) != ASCIICHAT_OK) {
465 return false;
466 }
467
468 // Attempt connection
469 int result = connect(sockfd, addr, addrlen);
470
471 if (result == 0) {
472 // Connected immediately
473 if (socket_set_blocking(sockfd) != 0) {
474 return false;
475 }
476 return true;
477 }
478
479 // Check if connection is in progress (expected for non-blocking sockets)
480 int error = socket_get_last_error();
482 log_warn("TCP connection could not start (socket error %d)", error);
483 return false;
484 }
485
486 // Use select to wait for connection with timeout
487 fd_set write_fds;
488 struct timeval timeout;
489
490 socket_fd_zero(&write_fds);
491 socket_fd_set(sockfd, &write_fds);
492
493 timeout.tv_sec = timeout_seconds;
494 timeout.tv_usec = 0;
495
496 result = socket_select(sockfd, NULL, &write_fds, NULL, &timeout);
497
498 if (result <= 0) {
499 log_warn("TCP connection wait failed (result %d, socket error %d)", result,
500 result == 0 ? 0 : socket_get_last_error());
501 return false; // Timeout or error
502 }
503
504 if (!socket_fd_isset(sockfd, &write_fds)) {
505 return false; // Socket not ready
506 }
507
508 // Check if connection was successful
509 int error_code = 0;
510 socklen_t error_len = sizeof(error_code);
511
512 if (socket_getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error_code, &error_len) != 0) {
513 return false;
514 }
515
516 if (error_code != 0) {
517 log_warn("TCP connection failed (socket error %d)", error_code);
518 return false;
519 }
520
521 // Connection successful - restore blocking mode
522 if (socket_set_blocking(sockfd) != 0) {
523 log_warn("Failed to restore socket to blocking mode after connect");
524 // Continue anyway - non-blocking mode works but may cause issues with send/recv
525 }
526
527 return true;
528}
@ ASCIICHAT_OK
Definition error_codes.h:51
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
asciichat_error_t set_socket_nonblocking(socket_t sockfd)
Set socket non-blocking.
void socket_fd_zero(fd_set *set)
Clear an fd_set.
int socket_getsockopt(socket_t sock, int level, int optname, void *optval, socklen_t *optlen)
Get socket option.
void socket_fd_set(socket_t sock, fd_set *set)
Add a socket to an fd_set.
int socket_set_blocking(socket_t sock)
Set socket to blocking mode.
bool socket_is_would_block_error(int error_code)
Check if error code indicates "would block" (non-blocking socket would wait)
bool socket_is_in_progress_error(int error_code)
Check if error indicates operation in progress (non-blocking connect)
int socket_select(socket_t max_fd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
Select sockets for I/O readiness.
#define EBADF
int errno
int socket_fd_isset(socket_t sock, fd_set *set)
Check if a socket is in an fd_set.

References ASCIICHAT_OK, EBADF, errno, error_code, INVALID_SOCKET_VALUE, log_warn, set_socket_nonblocking(), socket_fd_isset(), socket_fd_set(), socket_fd_zero(), socket_get_last_error(), socket_getsockopt(), socket_is_in_progress_error(), socket_is_would_block_error(), socket_select(), and socket_set_blocking().

Referenced by tcp_client_connect().

◆ dns_test_connectivity()

bool dns_test_connectivity ( const char *  hostname)

#include <dns.h>

Test DNS connectivity by resolving a hostname.

Attempts to resolve the given hostname to verify DNS connectivity. Uses a short timeout to avoid blocking.

Parameters
hostnameHostname to resolve (e.g., "api.github.com")
Returns
true if DNS resolution succeeds, false otherwise
Note
Logs warnings on failure
IPv4 resolution only (AF_INET)

Definition at line 11 of file dns.c.

11 {
12 if (!hostname) {
13 log_warn("NULL hostname provided to DNS connectivity test");
14 return false;
15 }
16
17 struct addrinfo hints, *result = NULL;
18 memset(&hints, 0, sizeof(hints));
19 hints.ai_family = AF_INET; // IPv4
20 hints.ai_socktype = SOCK_STREAM;
21
22 log_debug("Testing DNS connectivity to %s...", hostname);
23 log_info("★ ABOUT TO CALL getaddrinfo for %s", hostname);
24
25 // Use NULL for port to avoid musl strtoul parsing bug with string port numbers
26 // The actual port doesn't matter for connectivity testing - we just need DNS resolution
27 int ret = getaddrinfo(hostname, NULL, &hints, &result);
28 log_info("★ getaddrinfo returned %d", ret);
29 if (ret != 0) {
30 log_warn("DNS resolution failed for %s: %s", hostname, gai_strerror(ret));
31 return false;
32 }
33
34 if (result) {
35 freeaddrinfo(result);
36 }
37
38 log_debug("DNS connectivity test succeeded for %s", hostname);
39 return true;
40}
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548

References log_debug, log_info, and log_warn.

Referenced by update_check_perform().

◆ frame_check_size_overflow()

asciichat_error_t frame_check_size_overflow ( size_t  header_size,
size_t  data_size 
)

#include <frame_validator.h>

Check for overflow when adding header to data.

Parameters
header_sizeHeader size in bytes
data_sizeData size in bytes
Returns
ASCIICHAT_OK if safe, ERROR_BUFFER_OVERFLOW if would overflow

Definition at line 15 of file network/frame_validator.c.

15 {
16 if (data_size > SIZE_MAX - header_size) {
17 char size_str[32];
18 format_bytes_pretty(data_size, size_str, sizeof(size_str));
19 SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Frame size overflow: %s", size_str);
21 }
22 return ASCIICHAT_OK;
23}
@ ERROR_BUFFER_OVERFLOW
void format_bytes_pretty(size_t bytes, char *out, size_t out_capacity)
Format byte count into human-readable string.
Definition util/format.c:10

References ASCIICHAT_OK, ERROR_BUFFER_OVERFLOW, format_bytes_pretty(), and SET_ERRNO.

Referenced by frame_validate_legacy(), frame_validate_new(), and handle_image_frame_packet().

◆ frame_extract_dimensions()

void frame_extract_dimensions ( const void *  data,
uint32_t *  width,
uint32_t *  height 
)

#include <frame_validator.h>

Extract width and height from frame header.

Parameters
dataPacket data (must be at least 8 bytes)
widthOutput: frame width
heightOutput: frame height

Definition at line 86 of file network/frame_validator.c.

86 {
87 uint32_t width_net, height_net;
88 memcpy(&width_net, data, FRAME_HEADER_FIELD_SIZE);
89 memcpy(&height_net, (char *)data + FRAME_HEADER_FIELD_SIZE, FRAME_HEADER_FIELD_SIZE);
90 *width = NET_TO_HOST_U32(width_net);
91 *height = NET_TO_HOST_U32(height_net);
92}
#define NET_TO_HOST_U32(val)
Definition endian.h:81
#define FRAME_HEADER_FIELD_SIZE
Size of each header field (uint32_t = 4 bytes)

References FRAME_HEADER_FIELD_SIZE, and NET_TO_HOST_U32.

◆ frame_extract_new_header()

void frame_extract_new_header ( const void *  data,
uint32_t *  compressed,
uint32_t *  data_size 
)

#include <frame_validator.h>

Extract compressed flag and data size from new format header.

Parameters
dataPacket data (must be at least 16 bytes)
compressedOutput: whether data is compressed
data_sizeOutput: size of data field

Definition at line 94 of file network/frame_validator.c.

94 {
95 uint32_t compressed_net, size_net;
96 memcpy(&compressed_net, (char *)data + FRAME_HEADER_FIELD_SIZE * 2, FRAME_HEADER_FIELD_SIZE);
97 memcpy(&size_net, (char *)data + FRAME_HEADER_FIELD_SIZE * 3, FRAME_HEADER_FIELD_SIZE);
98 *compressed = NET_TO_HOST_U32(compressed_net);
99 *data_size = NET_TO_HOST_U32(size_net);
100}

References FRAME_HEADER_FIELD_SIZE, and NET_TO_HOST_U32.

Referenced by frame_validate_new().

◆ frame_validate_legacy()

asciichat_error_t frame_validate_legacy ( size_t  len,
size_t  expected_rgb_size 
)

#include <frame_validator.h>

Validate legacy frame format.

Parameters
lenTotal packet length
expected_rgb_sizeExpected RGB data size (width * height * 3)
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 25 of file network/frame_validator.c.

25 {
26 // Check minimum header size
27 if (len < FRAME_HEADER_SIZE_LEGACY) {
28 SET_ERRNO(ERROR_INVALID_FRAME, "Legacy frame header too small: %zu bytes", len);
30 }
31
32 // Check for overflow
34 if (err != ASCIICHAT_OK) {
35 return err;
36 }
37
38 size_t expected_total = FRAME_HEADER_SIZE_LEGACY + expected_rgb_size;
39 if (len != expected_total) {
40 SET_ERRNO(ERROR_INVALID_FRAME, "Legacy frame length mismatch: expected %zu got %zu", expected_total, len);
42 }
43
44 return ASCIICHAT_OK;
45}
@ ERROR_INVALID_FRAME
asciichat_error_t frame_check_size_overflow(size_t header_size, size_t data_size)
Check for overflow when adding header to data.
#define FRAME_HEADER_SIZE_LEGACY
Legacy frame header size (width:4 + height:4)

References ASCIICHAT_OK, ERROR_INVALID_FRAME, frame_check_size_overflow(), FRAME_HEADER_SIZE_LEGACY, and SET_ERRNO.

Referenced by handle_image_frame_packet().

◆ frame_validate_new()

asciichat_error_t frame_validate_new ( void *  data,
size_t  len,
bool *  out_compressed,
uint32_t *  out_data_size 
)

#include <frame_validator.h>

Validate new frame format with optional compression.

Parameters
dataPacket data
lenTotal packet length
out_compressedOutput: whether data is compressed (optional)
out_data_sizeOutput: size of compressed/uncompressed data (optional)
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 47 of file network/frame_validator.c.

47 {
48 // Check minimum new format header size
49 if (len < FRAME_HEADER_SIZE_NEW) {
50 SET_ERRNO(ERROR_INVALID_FRAME, "New frame header too small: %zu bytes", len);
52 }
53
54 uint32_t compressed_flag, data_size;
55 frame_extract_new_header(data, &compressed_flag, &data_size);
56
57 size_t data_size_sz = (size_t)data_size;
58
59 // Check data size against maximum
60 if (data_size_sz > IMAGE_MAX_PIXELS_SIZE) {
61 char size_str[32];
62 format_bytes_pretty(data_size_sz, size_str, sizeof(size_str));
63 SET_ERRNO(ERROR_INVALID_FRAME, "Frame data too large: %s", size_str);
65 }
66
67 // Check for overflow
69 if (err != ASCIICHAT_OK) {
70 return err;
71 }
72
73 size_t expected_total = FRAME_HEADER_SIZE_NEW + data_size_sz;
74 if (len != expected_total) {
75 SET_ERRNO(ERROR_INVALID_FRAME, "New frame length mismatch: expected %zu got %zu", expected_total, len);
77 }
78
79 if (out_compressed)
80 *out_compressed = (compressed_flag != 0);
81 if (out_data_size)
82 *out_data_size = data_size;
83 return ASCIICHAT_OK;
84}
void frame_extract_new_header(const void *data, uint32_t *compressed, uint32_t *data_size)
Extract compressed flag and data size from new format header.
#define IMAGE_MAX_PIXELS_SIZE
Maximum pixel data size in bytes.
#define FRAME_HEADER_SIZE_NEW
New frame header size (width:4 + height:4 + compressed:4 + size:4)

References ASCIICHAT_OK, ERROR_INVALID_FRAME, format_bytes_pretty(), frame_check_size_overflow(), frame_extract_new_header(), FRAME_HEADER_SIZE_NEW, IMAGE_MAX_PIXELS_SIZE, and SET_ERRNO.

◆ https_get()

char * https_get ( const char *  hostname,
const char *  path 
)

#include <http_client.h>

Perform HTTPS GET request.

Parameters
hostnameServer hostname (e.g., "github.com", must not be NULL)
pathResource path (e.g., "/username.keys", must not be NULL)
Returns
Allocated string containing response body (caller must free), or NULL on error

Makes a secure HTTPS connection to the specified hostname and fetches the resource at the given path. Uses system CA certificates for validation.

Note
TLS connection: Uses BearSSL for TLS connections. Validates server certificate against system CA certificates.
Memory management: Returns allocated string that caller must free. Use SAFE_FREE() to free the returned string.
Error handling: Returns NULL on error (network error, TLS error, etc.). Error messages are logged via logging system.
Hostname format: Hostname should be domain name only (no "https://" prefix). Example: "github.com", not "https://github.com".
Path format: Path should start with "/" for absolute paths. Example: "/username.keys", not "username.keys".
Response body: Returns complete response body as null-terminated string. Response may contain multiple keys (one per line for SSH keys).
Warning
Network dependency: Requires network connectivity and valid hostname. May fail if network is unavailable or hostname is unreachable.
Memory leak: Caller must free returned string using SAFE_FREE(). Function allocates memory that must be freed.
Certificate validation: Uses system CA certificates for validation. May fail if system CA certificates are missing or outdated.

Definition at line 178 of file http_client.c.

178 {
179 if (!hostname || !path) {
180 log_error("Invalid arguments to https_get");
181 return NULL;
182 }
183
184 log_info("HTTPS GET https://%s%s", hostname, path);
185
186 // Load system CA certificates
187 char *pem_data = NULL;
188 size_t pem_size = 0;
189 if (platform_load_system_ca_certs(&pem_data, &pem_size) != 0) {
190 log_error("Failed to load system CA certificates");
191 return NULL;
192 }
193
194 // Parse PEM certificates into BearSSL trust anchors
196 size_t num_anchors = read_trust_anchors_from_memory(&anchors, (unsigned char *)pem_data, pem_size);
197 SAFE_FREE(pem_data);
198
199 if (num_anchors == 0) {
200 log_error("No trust anchors loaded");
201 // Free anchors before returning to prevent leak
202 goto cleanup_anchors;
203 }
204 log_info("Loaded %zu trust anchors", num_anchors);
205
206 // Resolve hostname using getaddrinfo (modern, non-deprecated API)
207 struct addrinfo hints, *result;
208 memset(&hints, 0, sizeof(hints));
209 hints.ai_family = AF_INET; // IPv4
210 hints.ai_socktype = SOCK_STREAM;
211 hints.ai_protocol = IPPROTO_TCP;
212
213 if (getaddrinfo(hostname, "443", &hints, &result) != 0) {
214 log_error("Failed to resolve hostname: %s", hostname);
215 goto cleanup_anchors;
216 }
217
218 // Create TCP socket
219 socket_t sock = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
220 if (sock == INVALID_SOCKET_VALUE) {
221 log_error("Failed to create socket");
222 freeaddrinfo(result);
223 goto cleanup_anchors;
224 }
225
226 // Set socket timeouts to avoid blocking indefinitely on stalled connections.
227 // Windows expects a DWORD timeout in milliseconds, while POSIX expects a
228 // struct timeval. Passing timeval to Winsock makes the timeout effectively
229 // a few milliseconds and commonly aborts the TLS handshake.
230#ifdef _WIN32
231 DWORD timeout_ms = 5000;
232 setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout_ms, sizeof(timeout_ms));
233 setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char *)&timeout_ms, sizeof(timeout_ms));
234#else
235 struct timeval timeout = {.tv_sec = 5, .tv_usec = 0};
236 setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, sizeof(timeout));
237 setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char *)&timeout, sizeof(timeout));
238#endif
239
240 // Connect to server
241 if (connect(sock, result->ai_addr, (int)result->ai_addrlen) != 0) {
242 log_error("Failed to connect to %s:443", hostname);
243 socket_close(sock);
244 freeaddrinfo(result);
245 goto cleanup_anchors;
246 }
247
248 freeaddrinfo(result);
249
250 log_info("Connected to %s:443", hostname);
251
252 // Initialize BearSSL X.509 minimal validator
253 br_x509_minimal_context xc;
254 br_x509_minimal_init(&xc, &br_sha256_vtable, anchors.buf, anchors.ptr);
255
256 // Initialize BearSSL client context
257 br_ssl_client_context sc;
258 br_ssl_client_init_full(&sc, &xc, anchors.buf, anchors.ptr);
259
260 // Set I/O buffer
261 unsigned char *iobuf;
262 iobuf = SAFE_MALLOC(BR_SSL_BUFSIZE_BIDI, unsigned char *);
263 br_ssl_engine_set_buffer(&sc.eng, iobuf, BR_SSL_BUFSIZE_BIDI, 1);
264
265 // Initialize I/O context
266 br_sslio_context ioc;
267 br_sslio_init(&ioc, &sc.eng, sock_read, &sock, sock_write, &sock);
268
269 // Start TLS handshake
270 br_ssl_client_reset(&sc, hostname, 0);
271
272 log_info("Starting TLS handshake with %s", hostname);
273
274 // Build HTTP request
275 char request[BUFFER_SIZE_LARGE];
276 int request_len = safe_snprintf(request, sizeof(request),
277 "GET %s HTTP/1.1\r\n"
278 "Host: %s\r\n"
279 "Connection: close\r\n"
280 "User-Agent: ascii-chat/" ASCII_CHAT_VERSION_STRING "\r\n"
281 "\r\n",
282 path, hostname);
283
284 // Send HTTP request over TLS
285 if (br_sslio_write_all(&ioc, request, (size_t)request_len) != 0) {
286 log_error("Failed to send HTTP request");
287 SAFE_FREE(iobuf);
288 socket_close(sock);
289 goto cleanup_anchors;
290 }
291
292 br_sslio_flush(&ioc);
293 log_info("Sent HTTP request");
294
295 // Read HTTP response
296 char *response_buf = NULL;
297 size_t response_capacity = 8192;
298 size_t response_len = 0;
299 response_buf = SAFE_MALLOC(response_capacity, char *);
300
301 while (1) {
302 // Ensure we have space to read
303 if (response_len + 1024 > response_capacity) {
304 response_capacity *= 2;
305 if (response_capacity > HTTPS_MAX_RESPONSE_SIZE) {
306 log_error("Response exceeds maximum size (%d bytes)", HTTPS_MAX_RESPONSE_SIZE);
307 SAFE_FREE(response_buf);
308 SAFE_FREE(iobuf);
309 socket_close(sock);
310 goto cleanup_anchors;
311 }
312 response_buf = SAFE_REALLOC(response_buf, response_capacity, char *);
313 }
314
315 // Read data (reserve 1 byte for null terminator)
316 int n = br_sslio_read(&ioc, response_buf + response_len, response_capacity - response_len - 1);
317 if (n < 0) {
318 // Check for TLS errors
319 int err = br_ssl_engine_last_error(&sc.eng);
320 if (err != BR_ERR_OK) {
321 log_error("TLS error: %d", err);
322 SAFE_FREE(response_buf);
323 SAFE_FREE(iobuf);
324 socket_close(sock);
325 goto cleanup_anchors;
326 }
327 // EOF or connection closed
328 break;
329 }
330 if (n == 0) {
331 break; // EOF
332 }
333
334 response_len += (size_t)n;
335 }
336
337 response_buf[response_len] = '\0';
338 log_info("Received %zu bytes", response_len);
339
340 // Close connection
341 br_sslio_close(&ioc);
342 socket_close(sock);
343
344 // Parse HTTP response
345 asciichat_error_t status = check_http_status(response_buf);
346 if (status != ASCIICHAT_OK) {
347 SAFE_FREE(response_buf);
348 SAFE_FREE(iobuf);
349 goto cleanup_anchors;
350 }
351
352 char *body = extract_http_body(response_buf, response_len);
353 SAFE_FREE(response_buf);
354 SAFE_FREE(iobuf);
355
356 // Cleanup trust anchors
357 for (size_t i = 0; i < anchors.ptr; i++) {
358 free_ta_contents(&anchors.buf[i]);
359 }
360 SAFE_FREE(anchors.buf);
361
362 return body;
363
364cleanup_anchors:
365 for (size_t i = 0; i < anchors.ptr; i++) {
366 free_ta_contents(&anchors.buf[i]);
367 }
368 SAFE_FREE(anchors.buf);
369 return NULL;
370}
#define BUFFER_SIZE_LARGE
Large buffer size (1024 bytes)
#define SAFE_REALLOC(ptr, size, cast)
Definition common.h:284
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
br_x509_trust_anchor * buf
Definition pem.h:79
#define ANCHOR_LIST_INIT
Initializer for anchor_list.
Definition pem.h:93
size_t read_trust_anchors_from_memory(anchor_list *dst, const unsigned char *pem_data, size_t pem_len)
Read trust anchors from PEM-encoded data in memory.
Definition pem.c:449
void free_ta_contents(br_x509_trust_anchor *ta)
Free the contents of a trust anchor.
Definition pem.c:428
size_t ptr
Definition pem.h:80
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
asciichat_error_t platform_load_system_ca_certs(char **pem_data_out, size_t *pem_size_out)
Load system CA certificates for TLS/HTTPS.
int socket_close(socket_t sock)
Close a socket.
#define HTTPS_MAX_RESPONSE_SIZE
Vector type for trust anchors.
Definition pem.h:78
#define ASCII_CHAT_VERSION_STRING
Definition version.h:10

References ANCHOR_LIST_INIT, ASCII_CHAT_VERSION_STRING, ASCIICHAT_OK, anchor_list::buf, BUFFER_SIZE_LARGE, free_ta_contents(), HTTPS_MAX_RESPONSE_SIZE, INVALID_SOCKET_VALUE, log_error, log_info, platform_load_system_ca_certs(), anchor_list::ptr, read_trust_anchors_from_memory(), SAFE_FREE, SAFE_MALLOC, SAFE_REALLOC, safe_snprintf(), and socket_close().

Referenced by parse_public_key(), and update_check_perform().

◆ network_error_string()

const char * network_error_string ( )

#include <network.h>

Get human-readable error string for network errors.

Returns
Error string describing last network error

Returns a human-readable description of the last network error. Useful for error logging and user-facing error messages.

Note
Error string is thread-local - each thread has its own error state.
Parameters
error_codeError code
Returns
Error string

Definition at line 445 of file network/network.c.

445 {
447}

References socket_get_error_string().

Referenced by add_client(), server_connection_establish(), socket_configure_buffers(), and tcp_client_connect().

◆ packet_decrypt_envelope()

asciichat_error_t packet_decrypt_envelope ( packet_envelope_t *  envelope,
void *  crypto_ctx 
)

#include <packet.h>

Decrypt a PACKET_TYPE_ENCRYPTED envelope and extract inner packet.

If the envelope contains PACKET_TYPE_ENCRYPTED, decrypts the payload and updates the envelope with the inner packet (type, data, length). Used by both TCP and non-socket (WebRTC) transports to handle encryption uniformly.

Parameters
envelopePacket envelope to decrypt (will be modified if encrypted)
crypto_ctxCrypto context for decryption (required if envelope is encrypted)
Returns
ASCIICHAT_OK on success, error code otherwise
Note
Updates envelope->type, envelope->data, envelope->len, and envelope->allocated_buffer to point to decrypted plaintext
Caller must free the updated envelope->allocated_buffer

Decrypt a PACKET_TYPE_ENCRYPTED envelope and extract inner packet.

Parameters
sockfdSocket file descriptor
Returns
0 on success, -1 on error

Decrypt a PACKET_TYPE_ENCRYPTED envelope and extract inner packet

Shared decryption logic for both TCP (receive_packet_secure) and non-socket (WebRTC) transports. Decrypts PACKET_TYPE_ENCRYPTED envelopes and updates the envelope with the inner packet type and data.

Parameters
envelopePacket envelope to decrypt (will be modified if encrypted)
crypto_ctxCrypto context for decryption (required if envelope is encrypted)
Returns
ASCIICHAT_OK on success, error code otherwise

Definition at line 842 of file packet.c.

842 {
843 if (!envelope) {
844 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid envelope");
845 }
846
847 // Only decrypt if this is an encrypted packet
848 if (envelope->type != PACKET_TYPE_ENCRYPTED) {
849 return ASCIICHAT_OK; // Not encrypted, nothing to do
850 }
851
852 if (!crypto_ctx) {
853 return SET_ERRNO(ERROR_CRYPTO, "Received encrypted packet but no crypto context");
854 }
855
856 uint8_t *ciphertext = (uint8_t *)envelope->data;
857 size_t ciphertext_len = envelope->len;
858
859 // Allocate plaintext buffer with extra space for decryption
860 size_t plaintext_size = ciphertext_len + 1024;
861 uint8_t *plaintext = buffer_pool_alloc(NULL, plaintext_size);
862 if (!plaintext) {
863 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate plaintext buffer for decryption");
864 }
865
866 size_t plaintext_len;
867 crypto_result_t result =
868 crypto_decrypt(crypto_ctx, ciphertext, ciphertext_len, plaintext, plaintext_size, &plaintext_len);
869
870 if (result != CRYPTO_OK) {
871 buffer_pool_free(NULL, plaintext, plaintext_size);
872 return SET_ERRNO(ERROR_CRYPTO, "Failed to decrypt packet: %s", crypto_result_to_string(result));
873 }
874
875 if (plaintext_len < sizeof(packet_header_t)) {
876 buffer_pool_free(NULL, plaintext, plaintext_size);
877 return SET_ERRNO(ERROR_CRYPTO, "Decrypted packet too small: %zu < %zu", plaintext_len, sizeof(packet_header_t));
878 }
879
880 // Parse inner packet header
881 const packet_header_t *inner_header = (const packet_header_t *)plaintext;
882 packet_type_t inner_type = NET_TO_HOST_U16(inner_header->type);
883 uint32_t inner_len = NET_TO_HOST_U32(inner_header->length);
884
885 // Validate inner packet length
886 if (inner_len != plaintext_len - sizeof(packet_header_t)) {
887 buffer_pool_free(NULL, plaintext, plaintext_size);
888 return SET_ERRNO(ERROR_CRYPTO, "Inner packet length mismatch: header=%u, actual=%zu", inner_len,
889 plaintext_len - sizeof(packet_header_t));
890 }
891
892 // Free the original encrypted buffer if it's different from plaintext
893 if (envelope->allocated_buffer && envelope->allocated_buffer != plaintext) {
894 buffer_pool_free(NULL, envelope->allocated_buffer, envelope->allocated_size);
895 }
896
897 // Update envelope with decrypted packet info
898 envelope->type = inner_type;
899 envelope->data = (uint8_t *)plaintext + sizeof(packet_header_t);
900 envelope->len = inner_len;
901 envelope->allocated_buffer = plaintext;
902 envelope->allocated_size = plaintext_size;
903
904 log_info("[PACKET_DECRYPT] 🔐 Decrypted PACKET_TYPE_ENCRYPTED: inner_type=%u (0x%04x), len=%u", inner_type,
905 inner_type, inner_len);
906
907 return ASCIICHAT_OK;
908}
#define NET_TO_HOST_U16(val)
Definition endian.h:111
const char * crypto_result_to_string(crypto_result_t result)
Convert crypto result to human-readable string.
crypto_result_t
Cryptographic operation result codes.
crypto_result_t crypto_decrypt(crypto_context_t *ctx, const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext_out, size_t plaintext_out_size, size_t *plaintext_len_out)
Decrypt data using XSalsa20-Poly1305.
@ ERROR_CRYPTO
Definition error_codes.h:96
packet_type_t
Network protocol packet type enumeration.
Definition packet.h:286
@ PACKET_TYPE_ENCRYPTED
Encrypted packet (after handshake completion)
Definition packet.h:333
void * allocated_buffer
Buffer that needs to be freed by caller (may be NULL if not allocated)
Definition packet.h:1139
size_t allocated_size
Size of allocated buffer in bytes.
Definition packet.h:1141
size_t len
Length of payload data in bytes.
Definition packet.h:1135
void * data
Packet payload data (decrypted and decompressed if applicable)
Definition packet.h:1133
packet_type_t type
Packet type (from packet_types.h)
Definition packet.h:1131
Network packet header structure.
Definition packet.h:598
uint32_t length
Payload data length in bytes (0 for header-only packets)
Definition packet.h:604
uint16_t type
Packet type (packet_type_t enumeration)
Definition packet.h:602

References packet_envelope_t::allocated_buffer, packet_envelope_t::allocated_size, ASCIICHAT_OK, buffer_pool_alloc(), buffer_pool_free(), crypto_decrypt(), CRYPTO_OK, crypto_result_to_string(), packet_envelope_t::data, ERROR_CRYPTO, ERROR_INVALID_PARAM, ERROR_MEMORY, packet_envelope_t::len, packet_header_t::length, log_info, NET_TO_HOST_U16, NET_TO_HOST_U32, PACKET_TYPE_ENCRYPTED, SET_ERRNO, packet_header_t::type, and packet_envelope_t::type.

Referenced by acip_client_receive_and_dispatch().

◆ packet_parse_error_message()

asciichat_error_t packet_parse_error_message ( const void *  data,
size_t  len,
asciichat_error_t *  out_error_code,
char *  message_buffer,
size_t  message_buffer_size,
size_t *  out_message_length 
)

#include <packet.h>

Parse an error packet payload into components.

Parameters
dataPacket payload buffer
lenPayload length in bytes
out_error_codeOutput pointer for asciichat_error_t value (must not be NULL)
message_bufferDestination buffer for message string (must not be NULL)
message_buffer_sizeSize of destination buffer in bytes (must be > 0)
out_message_lengthOptional output for message length reported by sender
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 957 of file packet.c.

959 {
960 if (!data || len < sizeof(error_packet_t) || !out_error_code || !message_buffer || message_buffer_size == 0) {
962 "Invalid parameters: data=%p len=%zu out_error_code=%p message_buffer=%p buffer_size=%zu", data,
963 len, out_error_code, message_buffer, message_buffer_size);
964 }
965
966 const error_packet_t *packet = (const error_packet_t *)data;
967 uint32_t raw_error_code = NET_TO_HOST_U32(packet->error_code);
968 uint32_t raw_message_length = NET_TO_HOST_U32(packet->message_length);
969
970 if (raw_message_length > MAX_ERROR_MESSAGE_LENGTH) {
971 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Error message length too large: %u", raw_message_length);
972 }
973
974 size_t total_required = sizeof(error_packet_t) + (size_t)raw_message_length;
975 if (total_required > len) {
976 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Error packet truncated: expected %zu bytes, have %zu", total_required,
977 len);
978 }
979
980 const uint8_t *message_bytes = (const uint8_t *)data + sizeof(error_packet_t);
981 size_t copy_len = raw_message_length;
982 if (copy_len >= message_buffer_size) {
983 copy_len = message_buffer_size - 1;
984 }
985
986 if (copy_len > 0) {
987 memcpy(message_buffer, message_bytes, copy_len);
988 }
989 message_buffer[copy_len] = '\0';
990
991 if (out_message_length) {
992 *out_message_length = raw_message_length;
993 }
994
995 *out_error_code = (asciichat_error_t)raw_error_code;
996 return ASCIICHAT_OK;
997}
@ ERROR_NETWORK_PROTOCOL
Definition error_codes.h:81
#define MAX_ERROR_MESSAGE_LENGTH
Maximum error message length (512 bytes)
Definition packet.h:124
Error packet structure carrying error code and textual description.
Definition packet.h:727
uint32_t message_length
Length of message payload in bytes (0-512)
Definition packet.h:731
uint32_t error_code
Error code from asciichat_error_t enumeration.
Definition packet.h:729

References ASCIICHAT_OK, error_packet_t::error_code, ERROR_INVALID_PARAM, ERROR_NETWORK_PROTOCOL, MAX_ERROR_MESSAGE_LENGTH, error_packet_t::message_length, NET_TO_HOST_U32, and SET_ERRNO.

◆ packet_parse_remote_log()

asciichat_error_t packet_parse_remote_log ( const void *  data,
size_t  len,
log_level_t *  out_level,
remote_log_direction_t *  out_direction,
uint16_t *  out_flags,
char *  message_buffer,
size_t  message_buffer_size,
size_t *  out_message_length 
)

#include <packet.h>

Parse a remote log packet payload into components.

Parameters
dataPacket payload buffer
lenPayload length in bytes
out_levelOutput pointer for log level (must not be NULL)
out_directionOutput pointer for direction (must not be NULL)
out_flagsOutput pointer for flags (must not be NULL)
message_bufferDestination buffer for message string (must not be NULL)
message_buffer_sizeSize of destination buffer in bytes (must be > 0)
out_message_lengthOptional output for message length reported by sender
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 1056 of file packet.c.

1059 {
1060 if (!data || len < sizeof(remote_log_packet_t) || !out_level || !out_direction || !out_flags || !message_buffer ||
1061 message_buffer_size == 0) {
1062 return SET_ERRNO(
1064 "Invalid parameters: data=%p len=%zu out_level=%p out_direction=%p out_flags=%p buffer=%p size=%zu", data, len,
1065 out_level, out_direction, out_flags, message_buffer, message_buffer_size);
1066 }
1067
1068 const remote_log_packet_t *packet = (const remote_log_packet_t *)data;
1069 uint8_t raw_level = packet->log_level;
1070 if (raw_level > LOG_FATAL) {
1071 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid remote log level: %u", raw_level);
1072 }
1073 *out_level = (log_level_t)raw_level;
1074
1075 uint8_t raw_direction = packet->direction;
1076 if (raw_direction > REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER) {
1077 raw_direction = REMOTE_LOG_DIRECTION_UNKNOWN;
1078 }
1079 *out_direction = (remote_log_direction_t)raw_direction;
1080
1081 uint16_t raw_flags = NET_TO_HOST_U16(packet->flags);
1082 *out_flags = raw_flags;
1083
1084 uint32_t raw_message_length = NET_TO_HOST_U32(packet->message_length);
1085 if (raw_message_length > MAX_REMOTE_LOG_MESSAGE_LENGTH) {
1086 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Remote log message length too large: %u", raw_message_length);
1087 }
1088
1089 size_t total_required = sizeof(remote_log_packet_t) + (size_t)raw_message_length;
1090 if (total_required > len) {
1091 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Remote log packet truncated: expected %zu bytes, have %zu",
1092 total_required, len);
1093 }
1094
1095 const uint8_t *message_bytes = (const uint8_t *)data + sizeof(remote_log_packet_t);
1096 size_t copy_len = raw_message_length;
1097 if (copy_len >= message_buffer_size) {
1098 copy_len = message_buffer_size - 1;
1099 }
1100
1101 if (copy_len > 0) {
1102 memcpy(message_buffer, message_bytes, copy_len);
1103 }
1104 message_buffer[copy_len] = '\0';
1105
1106 if (out_message_length) {
1107 *out_message_length = raw_message_length;
1108 }
1109
1110 return ASCIICHAT_OK;
1111}
enum remote_log_direction remote_log_direction_t
Remote log packet direction enumeration.
log_level_t
Logging levels enumeration.
Definition types.h:29
@ REMOTE_LOG_DIRECTION_UNKNOWN
Definition network/log.h:21
@ REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER
Definition network/log.h:23
#define MAX_REMOTE_LOG_MESSAGE_LENGTH
Maximum remote log message length (512 bytes)
Definition packet.h:134
Remote log packet structure carrying log level and message text.
Definition packet.h:741
uint16_t flags
Additional flags (REMOTE_LOG_FLAG_*)
Definition packet.h:747
uint32_t message_length
Message payload length in bytes (0-512)
Definition packet.h:749
uint8_t log_level
Log level associated with the message (log_level_t cast to uint8_t)
Definition packet.h:743
uint8_t direction
Direction hint so receivers can annotate origin.
Definition packet.h:745
#define LOG_FATAL
Definition types.h:43

References ASCIICHAT_OK, remote_log_packet_t::direction, ERROR_INVALID_PARAM, ERROR_NETWORK_PROTOCOL, remote_log_packet_t::flags, LOG_FATAL, remote_log_packet_t::log_level, MAX_REMOTE_LOG_MESSAGE_LENGTH, remote_log_packet_t::message_length, NET_TO_HOST_U16, NET_TO_HOST_U32, REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER, REMOTE_LOG_DIRECTION_UNKNOWN, and SET_ERRNO.

Referenced by handle_remote_log_packet_from_client().

◆ packet_receive()

asciichat_error_t packet_receive ( socket_t  sockfd,
packet_type_t *  type,
void **  data,
size_t *  len 
)

#include <packet.h>

Receive a packet with header validation and CRC32 checking.

Parameters
sockfdSocket file descriptor
typeOutput: Packet type
dataOutput: Packet payload data (allocated by function, freed by caller)
lenOutput: Payload data length in bytes
Returns
ASCIICHAT_OK on success, error code on failure

Receives a complete packet, validates header, and verifies CRC32 checksum. Allocates buffer for payload data which must be freed by caller.

Note
Allocates buffer for payload data - caller must free when done.
Validates magic number and CRC32 checksum before returning data.
Warning
Allocated data buffer must be freed by caller to prevent memory leaks.

Receive a packet with header validation and CRC32 checking.

Parameters
sockfdSocket file descriptor
typeOutput: packet type
dataOutput: packet data (allocated by caller, freed by caller)
lenOutput: data length
Returns
0 on success, -1 on error

Definition at line 352 of file packet.c.

352 {
353 if (sockfd == INVALID_SOCKET_VALUE) {
354 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid socket descriptor");
355 }
356 if (!type || !data || !len) {
357 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: type=%p, data=%p, len=%p", type, data, len);
358 }
359
360 // Read packet header into memory from network socket
361 packet_header_t header;
362 uint64_t header_timeout_ns = RECV_TIMEOUT * NS_PER_SEC_INT;
363 ssize_t received = recv_with_timeout(sockfd, &header, sizeof(header), header_timeout_ns);
364 if (received < 0) {
365 // Error context is already set by recv_with_timeout
366 return ERROR_NETWORK;
367 }
368 if ((size_t)received != sizeof(header)) {
369 if (received == 0) {
370 log_warn("Connection closed while reading packet header");
371 return SET_ERRNO(ERROR_NETWORK, "Connection closed by peer while reading packet header");
372 }
373
374 return SET_ERRNO(ERROR_NETWORK, "Partial packet header received: %zd/%zu bytes", received, sizeof(header));
375 }
376
377 // Validate packet header
378 uint16_t pkt_type;
379 uint32_t pkt_len;
380 uint32_t expected_crc;
381 if (packet_validate_header(&header, &pkt_type, &pkt_len, &expected_crc) != ASCIICHAT_OK) {
382 // Error context is already set by packet_validate_header
384 }
385
386 // Allocate buffer for payload
387 void *payload = NULL;
388 if (pkt_len > 0) {
389 payload = buffer_pool_alloc(NULL, pkt_len);
390
391 // Use adaptive timeout for large packets
392 uint64_t recv_timeout = calculate_packet_timeout(pkt_len);
393 received = recv_with_timeout(sockfd, payload, pkt_len, recv_timeout);
394 if (received < 0) {
395 buffer_pool_free(NULL, payload, pkt_len);
396 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to receive packet payload");
397 }
398 if (received != (ssize_t)pkt_len) {
399 buffer_pool_free(NULL, payload, pkt_len);
400 return SET_ERRNO(ERROR_NETWORK, "Partial packet payload received: %zd/%u bytes", received, pkt_len);
401 }
402
403 // Validate CRC32
404 if (packet_validate_crc32(payload, pkt_len, expected_crc) != ASCIICHAT_OK) {
405 buffer_pool_free(NULL, payload, pkt_len);
406 // Error context is already set by packet_validate_crc32
408 }
409 }
410
411 // Return results
412 *type = (packet_type_t)pkt_type;
413 *data = payload;
414 *len = pkt_len;
415
416 return ASCIICHAT_OK;
417}
unsigned long long uint64_t
Definition common.h:59
#define NS_PER_SEC_INT
Definition time.h:157
ssize_t recv_with_timeout(socket_t sockfd, void *buf, size_t len, uint64_t timeout_ns)
Receive data with timeout.
#define RECV_TIMEOUT
Receive timeout in seconds (1 second)

References ASCIICHAT_OK, buffer_pool_alloc(), buffer_pool_free(), ERROR_INVALID_PARAM, ERROR_NETWORK, ERROR_NETWORK_PROTOCOL, INVALID_SOCKET_VALUE, log_warn, NS_PER_SEC_INT, RECV_TIMEOUT, recv_with_timeout(), SET_ERRNO, and SET_ERRNO_SYS.

Referenced by nat_measure_bandwidth(), and receive_packet().

◆ packet_send()

asciichat_error_t packet_send ( socket_t  sockfd,
packet_type_t  type,
const void *  data,
size_t  len 
)

#include <packet.h>

Send a packet with header and CRC32 checksum.

Parameters
sockfdSocket file descriptor
typePacket type (from packet_types.h)
dataPacket payload data (can be NULL for header-only packets)
lenPayload data length in bytes
Returns
ASCIICHAT_OK on success, error code on failure

Sends a complete packet with header (magic, type, length, CRC32) and optional payload. CRC32 is computed automatically and included in header.

Note
Packet header is constructed with all required fields including magic number, type, length, CRC32, and client ID.
CRC32 is computed over payload data (or zero if data is NULL).

Send a packet with header and CRC32 checksum.

Parameters
sockfdSocket file descriptor
typePacket type
dataPacket data
lenData length
Returns
0 on success, -1 on error

Definition at line 292 of file packet.c.

292 {
293 if (sockfd == INVALID_SOCKET_VALUE) {
294 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid socket descriptor");
295 }
296
297 if (len > MAX_PACKET_SIZE) {
298 return SET_ERRNO(ERROR_NETWORK_SIZE, "Packet too large: %zu > %d", len, MAX_PACKET_SIZE);
299 }
300
302 .type = HOST_TO_NET_U16((uint16_t)type),
303 .length = HOST_TO_NET_U32((uint32_t)len),
304 .crc32 = HOST_TO_NET_U32(len > 0 ? asciichat_crc32(data, len) : 0),
305 .client_id = HOST_TO_NET_U32(0)}; // Always initialize client_id to 0 in network byte order
306
307 // Calculate timeout based on packet size (in nanoseconds)
308 uint64_t timeout = calculate_packet_timeout(len);
309
310 // Send header first
311 ssize_t sent = send_with_timeout(sockfd, &header, sizeof(header), timeout);
312 if (sent < 0) {
313 // Error context is already set by send_with_timeout
314 return ERROR_NETWORK;
315 }
316 if ((size_t)sent != sizeof(header)) {
317 return SET_ERRNO(ERROR_NETWORK, "Failed to fully send packet header. Sent %zd/%zu bytes", sent, sizeof(header));
318 }
319
320 // Send payload if present
321 if (len > 0 && data) {
322 // Check socket validity before sending payload to avoid race conditions
323 if (!socket_is_valid(sockfd)) {
324 return SET_ERRNO(ERROR_NETWORK, "Socket became invalid between header and payload send");
325 }
326 sent = send_with_timeout(sockfd, data, len, timeout);
327 // Check for error first to avoid signed/unsigned comparison issues
328 if (sent < 0) {
329 // Error context is already set by send_with_timeout
330 return ERROR_NETWORK;
331 }
332 if ((size_t)sent != len) {
333 return SET_ERRNO(ERROR_NETWORK, "Failed to fully send packet payload. Sent %zd/%zu bytes", sent, len);
334 }
335 }
336
337#ifdef DEBUG_NETWORK
338 log_debug("Sent packet type=%d, len=%zu, errno=%d (%s)", type, len, errno, SAFE_STRERROR(errno));
339#endif
340
341 return 0;
342}
#define HOST_TO_NET_U64(val)
Definition endian.h:213
#define SAFE_STRERROR(errnum)
Definition common.h:465
@ ERROR_NETWORK_SIZE
Definition error_codes.h:82
ssize_t send_with_timeout(socket_t sockfd, const void *data, size_t len, uint64_t timeout_ns)
Send data with timeout using chunked transmission.
#define MAX_PACKET_SIZE
Maximum packet size (5MB)
Definition packet.h:115
#define PACKET_MAGIC
Packet magic number (alias for MAGIC_PACKET_VALID)
Definition packet.h:255
bool socket_is_valid(socket_t sock)
Check if a socket handle is valid.
#define asciichat_crc32(data, len)
Main CRC32 dispatcher macro - use this in application code.
Definition crc32.h:144
uint64_t magic
Magic number (PACKET_MAGIC = 0xA5C11C4A1 "ASCIICHAT" in hex) for packet validation.
Definition packet.h:600

References asciichat_crc32, errno, ERROR_INVALID_PARAM, ERROR_NETWORK, ERROR_NETWORK_SIZE, HOST_TO_NET_U16, HOST_TO_NET_U32, HOST_TO_NET_U64, INVALID_SOCKET_VALUE, log_debug, packet_header_t::magic, MAX_PACKET_SIZE, PACKET_MAGIC, SAFE_STRERROR, send_with_timeout(), SET_ERRNO, and socket_is_valid().

Referenced by discovery_session_check_host_alive(), nat_measure_bandwidth(), packet_send_error(), packet_send_remote_log(), send_image_frame_packet(), send_packet(), send_packet_secure(), session_host_broadcast_frame(), and session_host_send_frame().

◆ packet_send_error()

asciichat_error_t packet_send_error ( socket_t  sockfd,
const crypto_context_t *  crypto_ctx,
asciichat_error_t  error_code,
const char *  message 
)

#include <packet.h>

Send an error packet with optional encryption context.

Parameters
sockfdSocket file descriptor
crypto_ctxCrypto context for encryption (NULL or not ready sends plaintext)
error_codeError code from asciichat_error_t enumeration
messageHuman-readable message to accompany the error (can be NULL)
Returns
ASCIICHAT_OK on success, error code otherwise

When the crypto context is ready, the packet is encrypted automatically. During the handshake (or when encryption is disabled), the packet is sent in plaintext so protocol errors can be delivered before encryption is active.

Definition at line 910 of file packet.c.

911 {
912 if (sockfd == INVALID_SOCKET_VALUE) {
913 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid socket descriptor");
914 }
915
916 if (!message) {
917 message = "";
918 }
919
920 size_t message_len = strnlen(message, MAX_ERROR_MESSAGE_LENGTH);
921 if (message_len == MAX_ERROR_MESSAGE_LENGTH) {
922 log_warn("Error message truncated to %zu bytes", (size_t)MAX_ERROR_MESSAGE_LENGTH);
923 }
924
925 size_t payload_len = sizeof(error_packet_t) + message_len;
926 uint8_t *payload = SAFE_MALLOC(payload_len, uint8_t *);
927 if (!payload) {
928 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate %zu bytes for error packet", payload_len);
929 }
930
931 error_packet_t *packet = (error_packet_t *)payload;
933 packet->message_length = HOST_TO_NET_U32((uint32_t)message_len);
934
935 if (message_len > 0) {
936 memcpy(payload + sizeof(error_packet_t), message, message_len);
937 }
938
939 bool encryption_ready = crypto_ctx && crypto_is_ready(crypto_ctx);
940 asciichat_error_t send_result;
941
942 if (encryption_ready) {
943 send_result =
944 send_packet_secure(sockfd, PACKET_TYPE_ERROR_MESSAGE, payload, payload_len, (crypto_context_t *)crypto_ctx);
945 } else {
946 send_result = packet_send(sockfd, PACKET_TYPE_ERROR_MESSAGE, payload, payload_len);
947 }
948 SAFE_FREE(payload);
949
950 if (send_result != ASCIICHAT_OK) {
951 return SET_ERRNO(ERROR_NETWORK, "Failed to send error packet: %s", asciichat_error_string(send_result));
952 }
953
954 return ASCIICHAT_OK;
955}
bool crypto_is_ready(const crypto_context_t *ctx)
Check if key exchange is complete and ready for encryption.
asciichat_error_t packet_send(socket_t sockfd, packet_type_t type, const void *data, size_t len)
Send a packet with proper header and CRC32.
Definition packet.c:292
@ PACKET_TYPE_ERROR_MESSAGE
Error packet with asciichat_error_t code and human-readable message.
Definition packet.h:352
Cryptographic context structure.

References ASCIICHAT_OK, crypto_is_ready(), error_packet_t::error_code, error_code, ERROR_INVALID_PARAM, ERROR_MEMORY, ERROR_NETWORK, HOST_TO_NET_U32, INVALID_SOCKET_VALUE, log_warn, MAX_ERROR_MESSAGE_LENGTH, error_packet_t::message_length, packet_send(), PACKET_TYPE_ERROR_MESSAGE, SAFE_FREE, SAFE_MALLOC, send_packet_secure(), and SET_ERRNO.

Referenced by disconnect_client_for_bad_data().

◆ packet_send_remote_log()

asciichat_error_t packet_send_remote_log ( socket_t  sockfd,
const crypto_context_t *  crypto_ctx,
log_level_t  level,
remote_log_direction_t  direction,
uint16_t  flags,
const char *  message 
)

#include <packet.h>

Send a remote log packet with optional encryption context.

Parameters
sockfdSocket file descriptor
crypto_ctxCrypto context for encryption (NULL or not ready sends plaintext)
levelLog level to transmit
directionDirection flag (REMOTE_LOG_DIRECTION_*)
flagsAdditional flags (REMOTE_LOG_FLAG_*)
messageLog message text
Returns
ASCIICHAT_OK on success, error code otherwise

Definition at line 999 of file packet.c.

1000 {
1001 if (sockfd == INVALID_SOCKET_VALUE) {
1002 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid socket descriptor");
1003 }
1004
1005 if (level < LOG_DEV || level > LOG_FATAL) {
1006 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid log level: %d", level);
1007 }
1008
1009 const char *safe_message = message ? message : "";
1010 bool truncated = false;
1011 size_t message_len = strnlen(safe_message, MAX_REMOTE_LOG_MESSAGE_LENGTH);
1012 if (message_len == MAX_REMOTE_LOG_MESSAGE_LENGTH && safe_message[message_len] != '\0') {
1013 truncated = true;
1014 }
1015
1016 size_t payload_len = sizeof(remote_log_packet_t) + message_len;
1017 uint8_t *payload = SAFE_MALLOC(payload_len, uint8_t *);
1018 if (!payload) {
1019 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate %zu bytes for remote log packet", payload_len);
1020 }
1021
1022 remote_log_packet_t *packet = (remote_log_packet_t *)payload;
1023 packet->log_level = (uint8_t)level;
1024 packet->direction = (uint8_t)direction;
1025 uint16_t final_flags = flags;
1026 if (truncated) {
1027 final_flags |= REMOTE_LOG_FLAG_TRUNCATED;
1028 }
1029 packet->flags = HOST_TO_NET_U16(final_flags);
1030 packet->message_length = HOST_TO_NET_U32((uint32_t)message_len);
1031
1032 if (message_len > 0) {
1033 memcpy(payload + sizeof(remote_log_packet_t), safe_message, message_len);
1034 }
1035
1036 bool encryption_ready = crypto_ctx && crypto_is_ready(crypto_ctx);
1037 asciichat_error_t send_result;
1038
1039 if (encryption_ready) {
1040 int secure_result =
1041 send_packet_secure(sockfd, PACKET_TYPE_REMOTE_LOG, payload, payload_len, (crypto_context_t *)crypto_ctx);
1042 send_result = secure_result == 0 ? ASCIICHAT_OK : ERROR_NETWORK;
1043 } else {
1044 send_result = packet_send(sockfd, PACKET_TYPE_REMOTE_LOG, payload, payload_len);
1045 }
1046
1047 SAFE_FREE(payload);
1048
1049 if (send_result != ASCIICHAT_OK) {
1050 return SET_ERRNO(ERROR_NETWORK, "Failed to send remote log packet: %d", send_result);
1051 }
1052
1053 return ASCIICHAT_OK;
1054}
#define REMOTE_LOG_FLAG_TRUNCATED
Remote log packet flag definitions.
Definition packet.h:736
@ PACKET_TYPE_REMOTE_LOG
Bidirectional remote logging packet.
Definition packet.h:354

References ASCIICHAT_OK, crypto_is_ready(), remote_log_packet_t::direction, ERROR_INVALID_PARAM, ERROR_MEMORY, ERROR_NETWORK, remote_log_packet_t::flags, HOST_TO_NET_U16, HOST_TO_NET_U32, INVALID_SOCKET_VALUE, LOG_FATAL, remote_log_packet_t::log_level, MAX_REMOTE_LOG_MESSAGE_LENGTH, remote_log_packet_t::message_length, packet_send(), PACKET_TYPE_REMOTE_LOG, REMOTE_LOG_FLAG_TRUNCATED, SAFE_FREE, SAFE_MALLOC, send_packet_secure(), and SET_ERRNO.

◆ receive_packet()

int receive_packet ( socket_t  sockfd,
packet_type_t *  type,
void **  data,
size_t *  len 
)

#include <packet.h>

Receive a basic packet without encryption.

Parameters
sockfdSocket file descriptor
typeOutput: Packet type
dataOutput: Packet payload data (allocated by function)
lenOutput: Payload data length in bytes
Returns
0 on success, -1 on error

Receives a plaintext packet without decryption. Allocates buffer for payload data which must be freed by caller.

Note
Use receive_packet_secure() if decryption support is needed.
Warning
Allocated data buffer must be freed by caller.
Parameters
sockfdSocket file descriptor
typeOutput: packet type
dataOutput: packet data
lenOutput: data length
Returns
0 on success, -1 on error

Definition at line 797 of file packet.c.

797 {
798 asciichat_error_t result = packet_receive(sockfd, type, data, len);
799 return result == ASCIICHAT_OK ? 0 : -1;
800}
asciichat_error_t packet_receive(socket_t sockfd, packet_type_t *type, void **data, size_t *len)
Receive a packet with proper header validation and CRC32 checking.
Definition packet.c:352

References ASCIICHAT_OK, and packet_receive().

Referenced by acds_client_handler(), acds_session_create(), acds_session_join(), acds_session_lookup(), discovery_session_process(), and server_crypto_handshake().

◆ receive_packet_secure()

packet_recv_result_t receive_packet_secure ( socket_t  sockfd,
void *  crypto_ctx,
bool  enforce_encryption,
packet_envelope_t *  envelope 
)

#include <packet.h>

Receive a packet with decryption and decompression support.

Parameters
sockfdSocket file descriptor
crypto_ctxCryptographic context for decryption (NULL for plaintext)
enforce_encryptionIf true, reject unencrypted packets (except handshake)
envelopeOutput: Received packet envelope with all metadata
Returns
PACKET_RECV_SUCCESS on success, error code on failure

Receives a packet with automatic decryption (if encrypted) and decompression (if compressed). Validates encryption policy and packet integrity.

Note
Decryption is applied automatically when packet is encrypted and crypto_ctx is provided.
Decompression is applied automatically when packet is compressed.
If enforce_encryption is true, unencrypted packets (except handshake packets) cause PACKET_RECV_SECURITY_VIOLATION.
Warning
Envelope's allocated_buffer must be freed by caller to prevent memory leaks.
Parameters
sockfdSocket file descriptor
crypto_ctxCrypto context for decryption
enforce_encryptionWhether to require encryption
envelopeOutput: received packet envelope
Returns
Packet receive result

Definition at line 569 of file packet.c.

570 {
571 return receive_packet_secure_with_timeout(sockfd, crypto_ctx, enforce_encryption, envelope,
573}
packet_recv_result_t receive_packet_secure_with_timeout(socket_t sockfd, void *crypto_ctx, bool enforce_encryption, packet_envelope_t *envelope, uint64_t timeout_ns)
Definition packet.c:575

References NS_PER_SEC_INT, receive_packet_secure_with_timeout(), and RECV_TIMEOUT.

Referenced by acip_server_receive_and_dispatch(), and add_client().

◆ receive_packet_secure_with_timeout()

packet_recv_result_t receive_packet_secure_with_timeout ( socket_t  sockfd,
void *  crypto_ctx,
bool  enforce_encryption,
packet_envelope_t *  envelope,
uint64_t  timeout_ns 
)

#include <packet.h>

Definition at line 575 of file packet.c.

576 {
577
578 if (!envelope) {
579 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: envelope=%p", envelope);
580 return PACKET_RECV_ERROR;
581 }
582
583 // Initialize envelope
584 memset(envelope, 0, sizeof(*envelope));
585
586 // Receive packet header
587 packet_header_t header;
588 ssize_t received = recv_with_timeout(sockfd, &header, sizeof(header), timeout_ns);
589
590 // Check for errors first (before comparing signed with unsigned)
591 if (received < 0) {
592 /* Preserve a poll timeout so callers can wait for the next signaling packet
593 * without treating an idle connection as a receive failure. */
594 asciichat_error_context_t error_context;
595 if (HAS_ERRNO(&error_context) && error_context.code == ERROR_NETWORK_TIMEOUT) {
596 return PACKET_RECV_ERROR;
597 }
598 SET_ERRNO(ERROR_NETWORK, "Failed to receive packet header: %zd/%zu bytes", received, sizeof(header));
599 return PACKET_RECV_ERROR;
600 }
601
602 if (received == 0) {
603 return PACKET_RECV_EOF;
604 }
605
606 if ((size_t)received != sizeof(header)) {
607 SET_ERRNO(ERROR_NETWORK, "Failed to receive packet header: %zd/%zu bytes", received, sizeof(header));
608 return PACKET_RECV_ERROR;
609 }
610
611 // Convert from network byte order
612 uint64_t magic = NET_TO_HOST_U64(header.magic);
613 uint16_t pkt_type = NET_TO_HOST_U16(header.type);
614 uint32_t pkt_len = NET_TO_HOST_U32(header.length);
615 uint32_t expected_crc = NET_TO_HOST_U32(header.crc32);
616
617 // DEBUG: Log raw header bytes for debugging packet misalignment
618 char header_hex[95];
619 for (size_t i = 0; i < sizeof(header); i++) {
620 snprintf(&header_hex[i * 2], 3, "%02x", ((uint8_t *)&header)[i]);
621 }
622 header_hex[94] = '\0';
623
624 // DEBUG: Log all received packet types in detail
625 log_info("[RECV_PKT_HEADER] 🔍 Received packet: type=%u (0x%04x), len=%u, client_id=%u, raw=%s", pkt_type, pkt_type,
626 pkt_len, header.client_id, header_hex);
627
628 // Validate magic number
629 if (magic != PACKET_MAGIC) {
630 SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid packet magic: 0x%llx (expected 0x%llx)", magic, PACKET_MAGIC);
631 return PACKET_RECV_ERROR;
632 }
633
634 // Validate packet size
635 if (pkt_len > MAX_PACKET_SIZE) {
636 SET_ERRNO(ERROR_NETWORK_SIZE, "Packet too large: %u > %d", pkt_len, MAX_PACKET_SIZE);
637 return PACKET_RECV_ERROR;
638 }
639
640 // Handle encrypted packets
641 if (pkt_type == PACKET_TYPE_ENCRYPTED) {
642 if (!crypto_ctx) {
643 SET_ERRNO(ERROR_CRYPTO, "Received encrypted packet but no crypto context");
644 return PACKET_RECV_ERROR;
645 }
646
647 // Read encrypted payload
648 uint8_t *ciphertext = buffer_pool_alloc(NULL, pkt_len);
649 if (!ciphertext) {
650 SET_ERRNO(ERROR_MEMORY, "Failed to allocate buffer for ciphertext");
651 return PACKET_RECV_ERROR;
652 }
653
654 uint64_t recv_timeout = MAX(timeout_ns, calculate_packet_timeout(pkt_len));
655 received = recv_with_timeout(sockfd, ciphertext, pkt_len, recv_timeout);
656 if (received != (ssize_t)pkt_len) {
657 SET_ERRNO(ERROR_NETWORK, "Failed to receive encrypted payload: %zd/%u bytes", received, pkt_len);
658 buffer_pool_free(NULL, ciphertext, pkt_len);
659 return PACKET_RECV_ERROR;
660 }
661
662 // Decrypt - allocate plaintext buffer with extra space
663 // pkt_len is already validated to be <= MAX_PACKET_SIZE, so adding 1024 cannot overflow
664 size_t plaintext_size = (size_t)pkt_len + 1024;
665 uint8_t *plaintext = buffer_pool_alloc(NULL, plaintext_size);
666 if (!plaintext) {
667 SET_ERRNO(ERROR_MEMORY, "Failed to allocate buffer for plaintext");
668 buffer_pool_free(NULL, ciphertext, pkt_len);
669 return PACKET_RECV_ERROR;
670 }
671
672 size_t plaintext_len;
673 crypto_result_t result = crypto_decrypt(crypto_ctx, ciphertext, pkt_len, plaintext, plaintext_size, &plaintext_len);
674 buffer_pool_free(NULL, ciphertext, pkt_len);
675
676 if (result != CRYPTO_OK) {
677 SET_ERRNO(ERROR_CRYPTO, "Failed to decrypt packet: %s", crypto_result_to_string(result));
678 buffer_pool_free(NULL, plaintext, plaintext_size);
679 return PACKET_RECV_ERROR;
680 }
681
682 if (plaintext_len < sizeof(packet_header_t)) {
683 SET_ERRNO(ERROR_CRYPTO, "Decrypted packet too small: %zu < %zu", plaintext_len, sizeof(packet_header_t));
684 buffer_pool_free(NULL, plaintext, plaintext_size);
685 return PACKET_RECV_ERROR;
686 }
687
688 // Parse decrypted header
689 packet_header_t *decrypted_header = (packet_header_t *)plaintext;
690 pkt_type = NET_TO_HOST_U16(decrypted_header->type);
691
692 // DEBUG: Log decrypted packet type
693 log_info("[RECV_PKT_DECRYPTED] 🔐 After decryption: actual_type=%u (0x%04x), len=%u", pkt_type, pkt_type,
694 NET_TO_HOST_U32(decrypted_header->length));
695 pkt_len = NET_TO_HOST_U32(decrypted_header->length);
696 expected_crc = NET_TO_HOST_U32(decrypted_header->crc32);
697
698 // Extract payload
699 size_t payload_len = plaintext_len - sizeof(packet_header_t);
700 if (payload_len != pkt_len) {
701 SET_ERRNO(ERROR_CRYPTO, "Decrypted payload size mismatch: %zu != %u", payload_len, pkt_len);
702 buffer_pool_free(NULL, plaintext, plaintext_size);
703 return PACKET_RECV_ERROR;
704 }
705
706 // Verify CRC
707 if (pkt_len > 0) {
708 uint32_t actual_crc = asciichat_crc32(plaintext + sizeof(packet_header_t), pkt_len);
709 if (actual_crc != expected_crc) {
710 SET_ERRNO(ERROR_CRYPTO, "Decrypted packet CRC mismatch: 0x%x != 0x%x", actual_crc, expected_crc);
711 buffer_pool_free(NULL, plaintext, plaintext_size);
712 return PACKET_RECV_ERROR;
713 }
714 }
715
716 // Set envelope
717 envelope->type = (packet_type_t)pkt_type;
718 envelope->data = plaintext + sizeof(packet_header_t);
719 envelope->len = pkt_len;
720 envelope->allocated_buffer = plaintext;
721 envelope->allocated_size = plaintext_size;
722
723 return PACKET_RECV_SUCCESS;
724 }
725
726 // Handle unencrypted packets
727 if (enforce_encryption && !packet_is_handshake_type(pkt_type)) {
728 SET_ERRNO(ERROR_CRYPTO, "Received unencrypted packet type %d but encryption is required", pkt_type);
729 return PACKET_RECV_ERROR;
730 }
731
732 // Read payload and return header+payload in envelope (matching encrypted path format)
733 size_t total_size = sizeof(packet_header_t) + pkt_len;
734 uint8_t *packet_buf = buffer_pool_alloc(NULL, total_size);
735 if (!packet_buf) {
736 SET_ERRNO(ERROR_MEMORY, "Failed to allocate buffer for packet");
737 return PACKET_RECV_ERROR;
738 }
739
740 // Copy header into buffer
741 memcpy(packet_buf, &header, sizeof(packet_header_t));
742
743 if (pkt_len > 0) {
744 uint64_t recv_timeout = calculate_packet_timeout(pkt_len);
745 received = recv_with_timeout(sockfd, packet_buf + sizeof(packet_header_t), pkt_len, recv_timeout);
746 if (received != (ssize_t)pkt_len) {
747 SET_ERRNO(ERROR_NETWORK, "Failed to receive payload: %zd/%u bytes", received, pkt_len);
748 buffer_pool_free(NULL, packet_buf, total_size);
749 return PACKET_RECV_ERROR;
750 }
751
752 // Verify CRC
753 uint32_t actual_crc = asciichat_crc32(packet_buf + sizeof(packet_header_t), pkt_len);
754 if (actual_crc != expected_crc) {
755 SET_ERRNO(ERROR_NETWORK, "Packet CRC mismatch: 0x%x != 0x%x", actual_crc, expected_crc);
756 buffer_pool_free(NULL, packet_buf, total_size);
757 return PACKET_RECV_ERROR;
758 }
759 }
760
761 envelope->data = packet_buf;
762 envelope->allocated_buffer = packet_buf;
763 envelope->allocated_size = total_size;
764 envelope->type = (packet_type_t)pkt_type;
765 envelope->len = total_size;
766
767 return PACKET_RECV_SUCCESS;
768}
#define NET_TO_HOST_U64(val)
Definition endian.h:228
#define HAS_ERRNO(var)
Check if an error occurred and get full context.
#define MAX(a, b)
Definition param.h:10
Error context structure.
uint32_t client_id
Client ID (0 = server, >0 = client identifier)
Definition packet.h:608
uint32_t crc32
CRC32 checksum of payload data (0 if length == 0)
Definition packet.h:606

References packet_envelope_t::allocated_buffer, packet_envelope_t::allocated_size, asciichat_crc32, buffer_pool_alloc(), buffer_pool_free(), packet_header_t::client_id, asciichat_error_context_t::code, packet_header_t::crc32, crypto_decrypt(), CRYPTO_OK, crypto_result_to_string(), packet_envelope_t::data, ERROR_CRYPTO, ERROR_INVALID_PARAM, ERROR_MEMORY, ERROR_NETWORK, ERROR_NETWORK_PROTOCOL, ERROR_NETWORK_SIZE, ERROR_NETWORK_TIMEOUT, HAS_ERRNO, packet_envelope_t::len, packet_header_t::length, log_info, packet_header_t::magic, MAX, MAX_PACKET_SIZE, NET_TO_HOST_U16, NET_TO_HOST_U32, NET_TO_HOST_U64, PACKET_MAGIC, PACKET_RECV_EOF, PACKET_RECV_ERROR, PACKET_RECV_SUCCESS, PACKET_TYPE_ENCRYPTED, recv_with_timeout(), SET_ERRNO, packet_header_t::type, and packet_envelope_t::type.

Referenced by receive_packet_secure().

◆ recv_with_timeout()

ssize_t recv_with_timeout ( socket_t  sockfd,
void *  buf,
size_t  len,
uint64_t  timeout_ns 
)

#include <network.h>

Receive data with timeout.

Parameters
sockfdSocket file descriptor
bufBuffer to receive data
lenLength of buffer in bytes
timeout_nsTimeout in nanoseconds
Returns
Number of bytes received on success, -1 on error

Receives data from socket with timeout support. Waits up to timeout_ns for data to arrive.

Note
Partial receives are possible - function returns number of bytes actually received.
Returns -1 on timeout or error. Use network_error_string() to get human-readable error description.
Parameters
sockfdSocket file descriptor
bufBuffer to receive data
lenLength of buffer
timeout_secondsTimeout in seconds
Returns
Number of bytes received, or -1 on error

Definition at line 182 of file network/network.c.

182 {
183 if (sockfd == INVALID_SOCKET_VALUE) {
184 errno = EBADF;
185 return -1;
186 }
187
188 ssize_t total_received = 0;
189 char *data = (char *)buf;
190
191 while (total_received < (ssize_t)len) {
192 // Set up poll for read timeout (in nanoseconds)
193 struct pollfd pfd;
194 pfd.fd = sockfd;
195 pfd.events = POLLIN;
196 pfd.revents = 0;
197
198 // Use provided timeout for recv() calls - don't artificially limit data packets in test mode
199 // Timeouts should only apply to initial handshake, not to receiving data on active connections
200 int result = socket_poll(&pfd, 1, (int64_t)timeout_ns);
201 if (result <= 0) {
202 if (result == 0) {
203 /* A read timeout is the ordinary idle state for signaling sockets.
204 * Do not emit an error on every polling interval. */
209 return -1;
210 }
211 if (network_handle_select_error(result)) {
212 continue; // Retry
213 }
214 return -1; // Fatal error
215 }
216
217 // Poll can return readiness for a hangup or socket error without POLLIN.
218 // Treat those as terminal network errors so callers tear down the dead
219 // connection instead of retrying it as an idle timeout in a tight loop.
220 if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
221 SET_ERRNO(ERROR_NETWORK, "recv_with_timeout poll reported socket flags 0x%x", pfd.revents);
222 return -1;
223 }
224
225 // Other readiness flags without POLLIN are an ordinary no-data result.
226 if (!(pfd.revents & POLLIN)) {
231 return -1;
232 }
233
234 // Calculate how much we still need to receive
235 size_t bytes_to_recv = len - (size_t)total_received;
236 ssize_t received = socket_recv(sockfd, data + total_received, bytes_to_recv, 0);
237
238 if (received < 0) {
239 int error = errno;
240 if (network_handle_recv_error(error)) {
241 continue; // Retry
242 }
243 return -1; // Fatal error
244 }
245
246 if (received == 0) {
247 // Connection closed by peer
248 log_debug("Connection closed by peer during recv");
249 return total_received; // Return what we got so far
250 }
251
252 total_received += received;
253 }
254
255 return total_received;
256}
ssize_t socket_recv(socket_t sock, void *buf, size_t len, int flags)
Receive data from a socket.

References asciichat_errno, asciichat_errno_context, asciichat_error_context_t::code, EBADF, errno, ERROR_NETWORK, ERROR_NETWORK_TIMEOUT, ETIMEDOUT, asciichat_error_context_t::has_system_error, INVALID_SOCKET_VALUE, log_debug, SET_ERRNO, socket_poll(), socket_recv(), and asciichat_error_context_t::system_errno.

Referenced by packet_receive(), and receive_packet_secure_with_timeout().

◆ send_crypto_capabilities_packet()

int send_crypto_capabilities_packet ( socket_t  sockfd,
const crypto_capabilities_packet_t *  caps 
)

#include <packet.h>

Send crypto capabilities packet.

Parameters
sockfdSocket file descriptor
capsCrypto capabilities packet structure
Returns
0 on success, -1 on error

Sends a PACKET_TYPE_CRYPTO_CAPABILITIES packet to advertise supported cryptographic algorithms (key exchange, authentication, cipher).

Note
Crypto capabilities packets are always sent unencrypted (handshake packets).
Parameters
sockfdSocket file descriptor
capsCrypto capabilities packet
Returns
0 on success, -1 on error

Definition at line 1133 of file packet.c.

1133 {
1134 if (!caps) {
1135 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: caps=%p", caps);
1136 return -1;
1137 }
1138 return send_packet(sockfd, PACKET_TYPE_CRYPTO_CAPABILITIES, caps, sizeof(*caps));
1139}
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_CRYPTO_CAPABILITIES
Client -> Server: Supported crypto algorithms (UNENCRYPTED)
Definition packet.h:307

References ERROR_INVALID_PARAM, PACKET_TYPE_CRYPTO_CAPABILITIES, send_packet(), and SET_ERRNO.

Referenced by discovery_session_process().

◆ send_crypto_parameters_packet()

int send_crypto_parameters_packet ( socket_t  sockfd,
const crypto_parameters_packet_t *  params 
)

#include <packet.h>

Send crypto parameters packet.

Parameters
sockfdSocket file descriptor
paramsCrypto parameters packet structure
Returns
0 on success, -1 on error

Sends a PACKET_TYPE_CRYPTO_PARAMETERS packet containing chosen cryptographic algorithms and data sizes for handshake continuation.

Note
Crypto parameters packets are always sent unencrypted (handshake packets).
Parameters
sockfdSocket file descriptor
paramsCrypto parameters packet
Returns
0 on success, -1 on error

Definition at line 1147 of file packet.c.

1147 {
1148 if (!params) {
1149 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: params=%p", params);
1150 return -1;
1151 }
1152
1153 // Create a copy and convert uint16_t fields to network byte order
1154 crypto_parameters_packet_t net_params = *params;
1155 log_debug("NETWORK_DEBUG: Before htons: kex=%u, auth=%u, sig=%u, secret=%u", params->kex_public_key_size,
1156 params->auth_public_key_size, params->signature_size, params->shared_secret_size);
1159 net_params.signature_size = HOST_TO_NET_U16(params->signature_size);
1161 log_debug("NETWORK_DEBUG: After htons: kex=%u, auth=%u, sig=%u, secret=%u", net_params.kex_public_key_size,
1162 net_params.auth_public_key_size, net_params.signature_size, net_params.shared_secret_size);
1163
1164 return send_packet(sockfd, PACKET_TYPE_CRYPTO_PARAMETERS, &net_params, sizeof(net_params));
1165}
@ PACKET_TYPE_CRYPTO_PARAMETERS
Server -> Client: Chosen algorithms + data sizes (UNENCRYPTED)
Definition packet.h:309
Crypto parameters packet structure (Packet Type 15)
Definition packet.h:981
uint16_t auth_public_key_size
Authentication public key size in bytes (e.g., 32 for Ed25519, 1952 for Dilithium3)
Definition packet.h:993
uint16_t signature_size
Signature size in bytes (e.g., 64 for Ed25519, 3309 for Dilithium3)
Definition packet.h:995
uint16_t shared_secret_size
Shared secret size in bytes (e.g., 32 for X25519)
Definition packet.h:997
uint16_t kex_public_key_size
Key exchange public key size in bytes (e.g., 32 for X25519, 1568 for Kyber1024)
Definition packet.h:991

References crypto_parameters_packet_t::auth_public_key_size, ERROR_INVALID_PARAM, HOST_TO_NET_U16, crypto_parameters_packet_t::kex_public_key_size, log_debug, PACKET_TYPE_CRYPTO_PARAMETERS, send_packet(), SET_ERRNO, crypto_parameters_packet_t::shared_secret_size, and crypto_parameters_packet_t::signature_size.

Referenced by server_crypto_handshake().

◆ send_image_frame_packet()

asciichat_error_t send_image_frame_packet ( socket_t  sockfd,
const void *  image_data,
uint16_t  width,
uint16_t  height,
uint8_t  format 
)

#include <packet.h>

Send image frame packet.

Parameters
sockfdSocket file descriptor
image_dataImage pixel data buffer
widthImage width in pixels
heightImage height in pixels
formatPixel format (0=RGB24)
Returns
ASCIICHAT_OK on success, error code on failure

Sends PACKET_TYPE_IMAGE_FRAME with RGB image data. Used for transmitting camera frames or video input.

Note
Maximum dimensions: 4096x4096 pixels (~25MB per frame).
Pixel data must be in RGB24 format (3 bytes per pixel).

Definition at line 1230 of file packet.c.

1231 {
1232 if (!image_data || width == 0 || height == 0) {
1233 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: image_data=%p, width=%u, height=%u", image_data, width,
1234 height);
1235 }
1236
1237 // Validate dimensions to prevent integer overflow
1238 // Max reasonable dimensions: 4K (3840x2160) = ~25MB per frame
1239 if (width > 4096 || height > 4096) {
1240 return SET_ERRNO(ERROR_INVALID_PARAM, "Image dimensions too large: %ux%u (max 4096x4096)", width, height);
1241 }
1242
1243 // Create image frame packet
1244 image_frame_packet_t packet;
1245 packet.width = width;
1246 packet.height = height;
1247 packet.pixel_format = format;
1248 packet.compressed_size = 0;
1249 packet.checksum = 0;
1250 packet.timestamp = 0; // Will be set by receiver
1251
1252 // Calculate total packet size
1253 // Cast to size_t before multiplication to prevent integer overflow
1254 // Use overflow-checked multiplication
1255 size_t width_times_height;
1256 if (checked_size_mul((size_t)width, (size_t)height, &width_times_height) != ASCIICHAT_OK) {
1257 return SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Image dimensions too large: %d x %d", width, height);
1258 }
1259
1260 size_t frame_size;
1261 if (checked_size_mul(width_times_height, 3u, &frame_size) != ASCIICHAT_OK) {
1262 return SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Frame size overflow for RGB format");
1263 }
1264
1265 size_t total_size;
1266 if (checked_size_add(sizeof(image_frame_packet_t), frame_size, &total_size) != ASCIICHAT_OK) {
1267 return SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Total packet size overflow");
1268 }
1269
1270 // Allocate buffer for complete packet
1271 void *packet_data = buffer_pool_alloc(NULL, total_size);
1272 if (!packet_data) {
1273 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate buffer for image frame packet: %zu bytes", total_size);
1274 }
1275
1276 // Copy packet header and image data
1277 memcpy(packet_data, &packet, sizeof(image_frame_packet_t));
1278 memcpy((char *)packet_data + sizeof(image_frame_packet_t), image_data, frame_size);
1279
1280 // Send packet
1281 asciichat_error_t result = packet_send(sockfd, PACKET_TYPE_IMAGE_FRAME, packet_data, total_size);
1282
1283 // Clean up
1284 buffer_pool_free(NULL, packet_data, total_size);
1285
1286 return result;
1287}
@ PACKET_TYPE_IMAGE_FRAME
Complete RGB image with dimensions.
Definition packet.h:363
Image frame packet structure (Packet Type 3)
Definition packet.h:876
uint32_t pixel_format
Pixel format enum (1=RGB24, 2=RGBA32, 3=BGR24, 4=BGRA32)
Definition packet.h:882
uint32_t timestamp
Timestamp when frame was captured (milliseconds since epoch)
Definition packet.h:888
uint32_t compressed_size
Compressed data size (0 = not compressed, >0 = compressed)
Definition packet.h:884
uint32_t checksum
CRC32 checksum of pixel data.
Definition packet.h:886
uint32_t height
Image height in pixels.
Definition packet.h:880
uint32_t width
Image width in pixels.
Definition packet.h:878

References ASCIICHAT_OK, buffer_pool_alloc(), buffer_pool_free(), image_frame_packet_t::checksum, image_frame_packet_t::compressed_size, ERROR_BUFFER_OVERFLOW, ERROR_INVALID_PARAM, ERROR_MEMORY, image_frame_packet_t::height, packet_send(), PACKET_TYPE_IMAGE_FRAME, image_frame_packet_t::pixel_format, SET_ERRNO, image_frame_packet_t::timestamp, and image_frame_packet_t::width.

◆ send_packet()

int send_packet ( socket_t  sockfd,
packet_type_t  type,
const void *  data,
size_t  len 
)

#include <packet.h>

Send a basic packet without encryption.

Parameters
sockfdSocket file descriptor
typePacket type (from packet_types.h)
dataPacket payload data
lenPayload data length in bytes
Returns
0 on success, -1 on error

Sends a plaintext packet without encryption. Equivalent to packet_send() but returns int instead of asciichat_error_t for compatibility.

Note
Use send_packet_secure() if encryption support is needed.
Parameters
sockfdSocket file descriptor
typePacket type
dataPacket data
lenData length
Returns
0 on success, -1 on error

Definition at line 784 of file packet.c.

784 {
785 asciichat_error_t result = packet_send(sockfd, type, data, len);
786 return result == ASCIICHAT_OK ? 0 : -1;
787}

References ASCIICHAT_OK, and packet_send().

Referenced by acds_session_create(), acds_session_join(), acds_session_lookup(), acip_server_send_error(), send_crypto_capabilities_packet(), send_crypto_parameters_packet(), send_error_packet_message(), send_ping_packet(), send_pong_packet(), send_protocol_version_packet(), and tcp_client_send_packet().

◆ send_packet_secure()

asciichat_error_t send_packet_secure ( socket_t  sockfd,
packet_type_t  type,
const void *  data,
size_t  len,
crypto_context_t *  crypto_ctx 
)

#include <packet.h>

Send a packet with encryption and compression support.

Parameters
sockfdSocket file descriptor
typePacket type (from packet_types.h)
dataPacket payload data
lenPayload data length in bytes
crypto_ctxCryptographic context for encryption (NULL for plaintext)
Returns
0 on success, negative on error

Sends a packet with automatic encryption (if crypto context provided) and compression (if packet is large enough). Handshake packets are never encrypted.

Note
Encryption is applied automatically when crypto_ctx is provided and packet type is not a handshake packet.
Compression is applied automatically to large packets based on size thresholds and compression ratios.
Handshake packets (packet_is_handshake_type(type) == true) are always sent unencrypted, even when crypto_ctx is provided.
Parameters
sockfdSocket file descriptor
typePacket type
dataPacket data
lenData length
crypto_ctxCrypto context for encryption
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 434 of file packet.c.

435 {
436 if (len > MAX_PACKET_SIZE) {
437 return SET_ERRNO(ERROR_NETWORK_SIZE, "Packet too large: %zu > %d", len, MAX_PACKET_SIZE);
438 }
439
440 // Handshake packets are ALWAYS sent unencrypted
441 if (packet_is_handshake_type(type)) {
442 return packet_send(sockfd, type, data, len);
443 }
444
445 // Apply compression if beneficial for large packets
446 const void *final_data = data;
447 size_t final_len = len;
448 void *compressed_data = NULL;
449
450 // Skip compression for pre-compressed data (Opus audio) or if --no-compress flag is set
451 bool should_skip_compression = packet_is_precompressed(type) || GET_OPTION(no_compress);
452 if (!should_skip_compression && len > COMPRESSION_MIN_SIZE && should_compress(len, len)) {
453 void *temp_compressed = NULL;
454 size_t compressed_size = 0;
455
456 // Use configured compression level from options (default: 1 for fastest compression)
457 int compression_level = (GET_OPTION(compression_level) > 0) ? GET_OPTION(compression_level) : 1;
458 asciichat_error_t compress_result = compress_data(data, len, &temp_compressed, &compressed_size, compression_level);
459 if (compress_result == ASCIICHAT_OK) {
460 double ratio = (double)compressed_size / (double)len;
461 if (ratio < COMPRESSION_RATIO_THRESHOLD) {
462 final_data = temp_compressed;
463 final_len = compressed_size;
464 compressed_data = temp_compressed;
465 log_debug("Compressed packet: %zu -> %zu bytes (%.1f%%)", len, compressed_size, ratio * 100.0);
466 } else {
467 SAFE_FREE(temp_compressed);
468 }
469 }
470 }
471
472 // If no crypto context or crypto not ready, send unencrypted
473 bool ready = crypto_ctx ? crypto_is_ready(crypto_ctx) : false;
474 if (!crypto_ctx || !ready) {
475 log_warn_every(LOG_RATE_FAST, "CRYPTO_DEBUG: Sending packet type %d UNENCRYPTED (crypto_ctx=%p, ready=%d)", type,
476 (void *)crypto_ctx, ready);
477 asciichat_error_t result = packet_send(sockfd, type, final_data, final_len);
478 if (compressed_data) {
479 SAFE_FREE(compressed_data);
480 }
481 if (result != ASCIICHAT_OK) {
482 SET_ERRNO(ERROR_NETWORK, "Failed to send packet: %s", asciichat_error_string(result));
483 }
484 return result;
485 }
486
487 // Encrypt the packet: create header + payload, encrypt everything, wrap in PACKET_TYPE_ENCRYPTED
489 .type = HOST_TO_NET_U16((uint16_t)type),
490 .length = HOST_TO_NET_U32((uint32_t)final_len),
491 .crc32 = HOST_TO_NET_U32(final_len > 0 ? asciichat_crc32(final_data, final_len) : 0),
492 .client_id = HOST_TO_NET_U32(0)}; // Always 0 - client_id is not used in practice
493
494 // Combine header + payload for encryption
495 // Check for integer overflow before addition
496 if (final_len > SIZE_MAX - sizeof(header)) {
497 if (compressed_data) {
498 SAFE_FREE(compressed_data);
499 }
500 return SET_ERRNO(ERROR_NETWORK_SIZE, "Packet too large: would overflow plaintext buffer size");
501 }
502 size_t plaintext_len = sizeof(header) + final_len;
503 uint8_t *plaintext = buffer_pool_alloc(NULL, plaintext_len);
504 if (!plaintext) {
505 if (compressed_data) {
506 SAFE_FREE(compressed_data);
507 }
508 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate buffer for plaintext packet");
509 }
510
511 memcpy(plaintext, &header, sizeof(header));
512 if (final_len > 0 && final_data) {
513 memcpy(plaintext + sizeof(header), final_data, final_len);
514 }
515
516 // Encrypt
517 // Check for integer overflow before calculating ciphertext size
518 if (plaintext_len > SIZE_MAX - CRYPTO_NONCE_SIZE - CRYPTO_MAC_SIZE) {
519 buffer_pool_free(NULL, plaintext, plaintext_len);
520 if (compressed_data) {
521 SAFE_FREE(compressed_data);
522 }
523 return SET_ERRNO(ERROR_NETWORK_SIZE, "Packet too large: would overflow ciphertext buffer size");
524 }
525 size_t ciphertext_size = plaintext_len + CRYPTO_NONCE_SIZE + CRYPTO_MAC_SIZE;
526 uint8_t *ciphertext = buffer_pool_alloc(NULL, ciphertext_size);
527 if (!ciphertext) {
528 buffer_pool_free(NULL, plaintext, plaintext_len);
529 if (compressed_data) {
530 SAFE_FREE(compressed_data);
531 }
532 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate buffer for ciphertext");
533 }
534
535 size_t ciphertext_len;
536 crypto_result_t result =
537 crypto_encrypt(crypto_ctx, plaintext, plaintext_len, ciphertext, ciphertext_size, &ciphertext_len);
538 buffer_pool_free(NULL, plaintext, plaintext_len);
539
540 if (result != CRYPTO_OK) {
541 buffer_pool_free(NULL, ciphertext, ciphertext_size);
542 if (compressed_data) {
543 SAFE_FREE(compressed_data);
544 }
545 return SET_ERRNO(ERROR_CRYPTO, "Failed to encrypt packet: %s", crypto_result_to_string(result));
546 }
547
548 // Send as PACKET_TYPE_ENCRYPTED
549 log_debug_every(LOG_RATE_SLOW, "CRYPTO_DEBUG: Sending encrypted packet (original type %d as PACKET_TYPE_ENCRYPTED)",
550 type);
551 asciichat_error_t send_result = packet_send(sockfd, PACKET_TYPE_ENCRYPTED, ciphertext, ciphertext_len);
552 buffer_pool_free(NULL, ciphertext, ciphertext_size);
553
554 if (compressed_data) {
555 SAFE_FREE(compressed_data);
556 }
557
558 return send_result;
559}
#define COMPRESSION_MIN_SIZE
Minimum packet size to attempt compression (1KB)
Definition compression.h:61
bool should_compress(size_t original_size, size_t compressed_size)
Determine if compression should be used for given data sizes.
Definition compression.c:74
asciichat_error_t compress_data(const void *input, size_t input_size, void **output, size_t *output_size, int compression_level)
Compress data using zstd with configurable compression level.
Definition compression.c:14
#define COMPRESSION_RATIO_THRESHOLD
Compression ratio threshold - only use if <80% original size.
Definition compression.h:58
crypto_result_t crypto_encrypt(crypto_context_t *ctx, const uint8_t *plaintext, size_t plaintext_len, uint8_t *ciphertext_out, size_t ciphertext_out_size, size_t *ciphertext_len_out)
Encrypt data using XSalsa20-Poly1305.
#define CRYPTO_NONCE_SIZE
Nonce size (XSalsa20)
#define CRYPTO_MAC_SIZE
MAC size (Poly1305)
#define LOG_RATE_FAST
Log rate limit: 1 second (1,000,000 microseconds)
Definition log_rates.h:26
#define LOG_RATE_SLOW
Log rate limit: 10 seconds (10,000,000 microseconds)
Definition log_rates.h:35
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
Definition log/log.h:702
#define log_warn_every(interval_us, fmt,...)
Rate-limited WARN logging.
Definition log/log.h:708
#define false
Definition stdbool.h:63

References asciichat_crc32, ASCIICHAT_OK, buffer_pool_alloc(), buffer_pool_free(), compress_data(), COMPRESSION_MIN_SIZE, COMPRESSION_RATIO_THRESHOLD, crypto_encrypt(), crypto_is_ready(), CRYPTO_MAC_SIZE, CRYPTO_NONCE_SIZE, CRYPTO_OK, crypto_result_to_string(), ERROR_CRYPTO, ERROR_MEMORY, ERROR_NETWORK, ERROR_NETWORK_SIZE, GET_OPTION, HOST_TO_NET_U16, HOST_TO_NET_U32, HOST_TO_NET_U64, log_debug, log_debug_every, LOG_RATE_FAST, LOG_RATE_SLOW, log_warn_every, packet_header_t::magic, MAX_PACKET_SIZE, PACKET_MAGIC, packet_send(), PACKET_TYPE_ENCRYPTED, SAFE_FREE, SET_ERRNO, and should_compress().

Referenced by av_send_audio_opus_batch(), packet_send_error(), and packet_send_remote_log().

◆ send_ping_packet()

int send_ping_packet ( socket_t  sockfd)

#include <packet.h>

Send a ping packet (keepalive)

Parameters
sockfdSocket file descriptor
Returns
0 on success, -1 on error

Sends a PACKET_TYPE_PING packet to keep connection alive. Server/client responds with PACKET_TYPE_PONG.

Note
Ping packets are always sent unencrypted (handshake packets).

Send a ping packet (keepalive)

Parameters
sockfdSocket file descriptor
Returns
0 on success, -1 on error

Definition at line 813 of file packet.c.

813 {
814 return send_packet(sockfd, PACKET_TYPE_PING, NULL, 0);
815}
@ PACKET_TYPE_PING
Keepalive ping packet.
Definition packet.h:383

References PACKET_TYPE_PING, and send_packet().

◆ send_pong_packet()

int send_pong_packet ( socket_t  sockfd)

#include <packet.h>

Send a pong packet (keepalive response)

Parameters
sockfdSocket file descriptor
Returns
0 on success, -1 on error

Sends a PACKET_TYPE_PONG packet in response to PACKET_TYPE_PING. Indicates that connection is alive and responsive.

Note
Pong packets are always sent unencrypted (handshake packets).

Send a pong packet (keepalive response)

Parameters
sockfdSocket file descriptor
Returns
0 on success, -1 on error

Definition at line 822 of file packet.c.

822 {
823 return send_packet(sockfd, PACKET_TYPE_PONG, NULL, 0);
824}
@ PACKET_TYPE_PONG
Keepalive pong response.
Definition packet.h:385

References PACKET_TYPE_PONG, and send_packet().

◆ send_protocol_version_packet()

int send_protocol_version_packet ( socket_t  sockfd,
const protocol_version_packet_t *  version 
)

#include <packet.h>

Send protocol version negotiation packet.

Parameters
sockfdSocket file descriptor
versionProtocol version packet structure
Returns
0 on success, -1 on error

Sends a PACKET_TYPE_PROTOCOL_VERSION packet for protocol capability negotiation. Both client and server send this during handshake.

Note
Protocol version packets are always sent unencrypted (handshake packets).

Send protocol version negotiation packet.

Parameters
sockfdSocket file descriptor
versionProtocol version packet
Returns
0 on success, -1 on error

Definition at line 1119 of file packet.c.

1119 {
1120 if (!version) {
1121 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: version=%p", version);
1122 return -1;
1123 }
1124 return send_packet(sockfd, PACKET_TYPE_PROTOCOL_VERSION, version, sizeof(*version));
1125}
@ PACKET_TYPE_PROTOCOL_VERSION
Protocol version and capabilities negotiation.
Definition packet.h:288

References ERROR_INVALID_PARAM, PACKET_TYPE_PROTOCOL_VERSION, send_packet(), and SET_ERRNO.

Referenced by discovery_session_process(), and server_crypto_handshake().

◆ send_with_timeout()

ssize_t send_with_timeout ( socket_t  sockfd,
const void *  data,
size_t  len,
uint64_t  timeout_ns 
)

#include <network.h>

Send data with timeout using chunked transmission.

Parameters
sockfdSocket file descriptor
dataData to send
lenLength of data in bytes
timeout_nsTimeout in nanoseconds
Returns
Number of bytes sent on success, -1 on error

Sends data to socket with timeout support. Uses chunked transmission for large data to prevent blocking and enable progress tracking.

Note
Chunked transmission automatically handles large frames by sending in smaller chunks, preventing timeout issues.
Partial sends are handled automatically - function retries until all data is sent or timeout occurs.
Parameters
sockfdSocket file descriptor
dataData to send
lenLength of data
timeout_secondsTimeout in seconds
Returns
Number of bytes sent, or -1 on error

Definition at line 112 of file network/network.c.

112 {
113 if (sockfd == INVALID_SOCKET_VALUE) {
114 errno = EBADF;
115 return -1;
116 }
117
118 size_t total_sent = 0;
119 const char *data_ptr = (const char *)data;
120
121 while (total_sent < len) {
122 // Calculate chunk size
123 size_t bytes_to_send = len - total_sent;
124 const size_t MAX_CHUNK_SIZE = 65536; // 64KB chunks for reliable TCP transmission
125 if (bytes_to_send > MAX_CHUNK_SIZE) {
126 bytes_to_send = MAX_CHUNK_SIZE;
127 }
128
129 // Set up poll for write timeout (in nanoseconds)
130 struct pollfd pfd;
131 pfd.fd = sockfd;
132 pfd.events = POLLOUT;
133 pfd.revents = 0;
134
135 int64_t effective_timeout_ns = network_is_test_environment() ? (100LL * NS_PER_MS_INT) : (int64_t)timeout_ns;
136 int result = socket_poll(&pfd, 1, effective_timeout_ns);
137 if (result <= 0) {
138 if (result == 0) {
139 SET_ERRNO_SYS(ERROR_NETWORK_TIMEOUT, "send_with_timeout timed out after %llu nanoseconds",
140 (unsigned long long)timeout_ns);
141 return -1;
142 }
143 if (network_handle_select_error(result)) {
144 continue; // Retry
145 }
146 return -1; // Fatal error
147 }
148
149 // Check if socket is ready for writing
150 if (!(pfd.revents & POLLOUT)) {
151 SET_ERRNO_SYS(ERROR_NETWORK_TIMEOUT, "send_with_timeout socket not ready for writing after poll");
152 return -1;
153 }
154
155 // Use platform-specific send
156 ssize_t sent = socket_send(sockfd, data_ptr + total_sent, bytes_to_send, 0);
157
158 if (sent < 0) {
159 int error = errno;
160 if (network_handle_send_error(error)) {
161 continue; // Retry
162 }
163 return -1; // Fatal error
164 }
165
166 if (sent > 0) {
167 total_sent += (size_t)sent;
168 }
169 }
170
171 return (ssize_t)total_sent;
172}
#define NS_PER_MS_INT
Definition time.h:156
#define network_is_test_environment()
Check if we're in a test environment.
ssize_t socket_send(socket_t sock, const void *buf, size_t len, int flags)
Send data on a socket.

References EBADF, errno, ERROR_NETWORK_TIMEOUT, INVALID_SOCKET_VALUE, network_is_test_environment, NS_PER_MS_INT, SET_ERRNO_SYS, socket_poll(), and socket_send().

Referenced by packet_send().

◆ set_socket_keepalive()

asciichat_error_t set_socket_keepalive ( socket_t  sockfd)

#include <network.h>

Enable TCP keepalive on socket.

Parameters
sockfdSocket file descriptor
Returns
ASCIICHAT_OK on success, error code on failure

Enables TCP keepalive probes on socket using KEEPALIVE_IDLE, KEEPALIVE_INTERVAL, and KEEPALIVE_COUNT settings.

Note
Keepalive helps detect dead connections (broken network path) without application-level heartbeats.

Enable TCP keepalive on socket.

Parameters
sockfdSocket file descriptor
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 371 of file network/network.c.

371 {
372 if (sockfd == INVALID_SOCKET_VALUE) {
373 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid socket file descriptor");
374 }
376 if (result != 0) {
377 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to set socket keepalive parameters");
378 }
379 return ASCIICHAT_OK;
380}
#define KEEPALIVE_COUNT
Keepalive probe count (8 probes)
#define KEEPALIVE_INTERVAL
Keepalive interval in seconds (10 seconds)
#define KEEPALIVE_IDLE
Keepalive idle time in seconds (60 seconds)
int socket_set_keepalive_params(socket_t sock, bool enable, int idle, int interval, int count)
Set TCP keepalive parameters.

References ASCIICHAT_OK, ERROR_INVALID_PARAM, ERROR_NETWORK, INVALID_SOCKET_VALUE, KEEPALIVE_COUNT, KEEPALIVE_IDLE, KEEPALIVE_INTERVAL, SET_ERRNO, SET_ERRNO_SYS, and socket_set_keepalive_params().

◆ set_socket_nonblocking()

asciichat_error_t set_socket_nonblocking ( socket_t  sockfd)

#include <network.h>

Set socket to non-blocking mode.

Parameters
sockfdSocket file descriptor
Returns
ASCIICHAT_OK on success, error code on failure

Sets socket to non-blocking mode. I/O operations return immediately with error if data is not available.

Note
Non-blocking sockets require different error handling - EAGAIN/EWOULDBLOCK indicates operation would block.
Useful for asynchronous I/O patterns with select/poll/epoll.

Set socket to non-blocking mode.

Parameters
sockfdSocket file descriptor
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 387 of file network/network.c.

387 {
388 if (sockfd == INVALID_SOCKET_VALUE) {
389 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid socket file descriptor");
390 }
391 int result = socket_set_nonblocking(sockfd, true);
392 if (result != 0) {
393 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to set socket non-blocking mode");
394 }
395 return ASCIICHAT_OK;
396}
int socket_set_nonblocking(socket_t sock, bool nonblocking)
Set socket to non-blocking mode.

References ASCIICHAT_OK, ERROR_INVALID_PARAM, ERROR_NETWORK, INVALID_SOCKET_VALUE, SET_ERRNO, SET_ERRNO_SYS, and socket_set_nonblocking().

Referenced by connect_with_timeout().

◆ set_socket_timeout()

asciichat_error_t set_socket_timeout ( socket_t  sockfd,
uint64_t  timeout_ns 
)

#include <network.h>

Set socket timeout for send/receive operations.

Parameters
sockfdSocket file descriptor
timeout_nsTimeout in nanoseconds (0 to disable timeout)
Returns
ASCIICHAT_OK on success, error code on failure

Configures socket-level timeout for send and receive operations. This sets SO_SNDTIMEO and SO_RCVTIMEO socket options.

Note
Socket-level timeouts work in addition to application-level timeouts in send_with_timeout() and recv_with_timeout().

Set socket timeout for send/receive operations.

Parameters
sockfdSocket file descriptor
timeout_nsTimeout in nanoseconds (converted to milliseconds for socket level)
Returns
ASCIICHAT_OK on success, error code on failure

Sets socket-level timeouts (SO_RCVTIMEO/SO_SNDTIMEO) as a safety net fallback. Actual precision depends on platform: milliseconds on most systems, microseconds on some. Works in conjunction with application-level timeouts via socket_poll.

Definition at line 336 of file network/network.c.

336 {
337 if (sockfd == INVALID_SOCKET_VALUE) {
338 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid socket file descriptor");
339 }
340
341 // Convert nanoseconds to milliseconds for socket-level timeout
342 // Note: socket-level timeouts have ~millisecond granularity on most platforms
343 uint64_t timeout_ms = timeout_ns / (1000 * 1000);
344 if (timeout_ms == 0 && timeout_ns > 0) {
345 timeout_ms = 1; // Ensure at least 1ms for non-zero timeouts
346 }
347
348 // Set both receive and send timeouts using struct timeval
349 struct timeval tv;
350 tv.tv_sec = (time_t)(timeout_ms / 1000);
351 tv.tv_usec = (long)((timeout_ms % 1000) * 1000);
352
353 // Set receive timeout
354 if (socket_setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) != 0) {
355 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to set socket receive timeout");
356 }
357
358 // Set send timeout
359 if (socket_setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) != 0) {
360 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to set socket send timeout");
361 }
362
363 return ASCIICHAT_OK;
364}
int socket_setsockopt(socket_t sock, int level, int optname, const void *optval, socklen_t optlen)
Set socket option.

References ASCIICHAT_OK, ERROR_INVALID_PARAM, ERROR_NETWORK, INVALID_SOCKET_VALUE, SET_ERRNO, SET_ERRNO_SYS, and socket_setsockopt().

Referenced by add_client().

◆ socket_configure_buffers()

asciichat_error_t socket_configure_buffers ( socket_t  sockfd)

#include <network.h>

Configure socket buffers and TCP_NODELAY for optimal performance.

Parameters
sockfdSocket file descriptor
Returns
ASCIICHAT_OK on success, or ERROR_NETWORK if all options fail

Configures socket with large send/receive buffers (1MB each) and enables TCP_NODELAY for low-latency real-time video streaming.

CONFIGURATION:

  • Send buffer: 1MB (SO_SNDBUF)
  • Receive buffer: 1MB (SO_RCVBUF)
  • TCP_NODELAY: enabled (disables Nagle's algorithm)

BEHAVIOR: Attempts to set all three socket options independently. Individual failures are logged as warnings but do not prevent attempting other options. This ensures TCP_NODELAY is always set even if buffer size configuration fails (common on systems with strict limits). Only returns error if ALL options fail.

Note
Large buffers prevent packet loss during frame bursts
TCP_NODELAY ensures frames are sent immediately without buffering
Call this immediately after socket creation/connection
Parameters
sockfdSocket file descriptor
Returns
ASCIICHAT_OK on success, ERROR_NETWORK_CONFIG on failure

Definition at line 403 of file network/network.c.

403 {
404 if (sockfd == INVALID_SOCKET_VALUE) {
405 return SET_ERRNO_SYS(ERROR_NETWORK, "Invalid socket file descriptor");
406 }
407
408 int failed_options = 0;
409
410 // Attempt to configure 1MB send buffer for optimal frame transmission
411 int send_buffer_size = 1024 * 1024;
412 if (socket_setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &send_buffer_size, sizeof(send_buffer_size)) < 0) {
413 log_warn("Failed to set send buffer size to 1MB: %s", network_error_string());
414 failed_options++;
415 }
416
417 // Attempt to configure 1MB receive buffer for optimal frame reception
418 int recv_buffer_size = 1024 * 1024;
419 if (socket_setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &recv_buffer_size, sizeof(recv_buffer_size)) < 0) {
420 log_warn("Failed to set receive buffer size to 1MB: %s", network_error_string());
421 failed_options++;
422 }
423
424 // Attempt to enable TCP_NODELAY to disable Nagle's algorithm for low-latency transmission
425 // This must be set even if buffer configuration fails, as it's essential for real-time video.
426 int tcp_nodelay = 1;
427 if (socket_setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &tcp_nodelay, sizeof(tcp_nodelay)) < 0) {
428 log_warn("Failed to set TCP_NODELAY: %s", network_error_string());
429 failed_options++;
430 }
431
432 // Return error only if ALL options failed
433 if (failed_options >= 3) {
434 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to configure all socket options");
435 }
436
437 return ASCIICHAT_OK;
438}
const char * network_error_string()
Get human-readable error string for network errors.

References ASCIICHAT_OK, ERROR_NETWORK, INVALID_SOCKET_VALUE, log_warn, network_error_string(), SET_ERRNO_SYS, and socket_setsockopt().

Referenced by server_connection_establish(), and tcp_client_connect().

◆ update_check_detect_install_method()

install_method_t update_check_detect_install_method ( void  )

#include <update_checker.h>

Detect installation method.

Checks binary path and system files to determine how ascii-chat was installed.

Returns
Installation method enum

Definition at line 380 of file update_checker.c.

380 {
381 if (is_homebrew_install()) {
383 }
384
385 if (is_arch_linux()) {
387 }
388
389 // Default to GitHub releases
391}

References INSTALL_METHOD_ARCH_AUR, INSTALL_METHOD_GITHUB, and INSTALL_METHOD_HOMEBREW.

Referenced by update_banner_print_instructions(), update_banner_show_prompt(), and update_check_format_notification().

◆ update_check_format_notification()

void update_check_format_notification ( const update_check_result_t *  result,
char *  buffer,
size_t  buffer_size 
)

#include <update_checker.h>

Format update notification message.

Creates human-readable update notification with version comparison and upgrade suggestion. Example: "Update available: v0.8.1 (f8dc35e1) → v0.9.0 (a1b2c3d4). Run: brew upgrade ascii-chat"

Parameters
resultUpdate check result
[out]bufferOutput buffer for formatted message
buffer_sizeSize of output buffer

Definition at line 437 of file update_checker.c.

437 {
438 if (!result || !buffer || buffer_size == 0) {
439 return;
440 }
441
442 // Get upgrade suggestion
444 char suggestion[512];
445 update_check_get_upgrade_suggestion(method, result->latest_version, suggestion, sizeof(suggestion));
446
447 // Keep the notification useful even when it is displayed in a log line:
448 // identify the versions, provide an actionable command or download URL, and
449 // point to the exact GitHub release that was returned by the API.
450 snprintf(buffer, buffer_size, "Update available: %s → %s. %s%s. Release notes: %s", result->current_version,
451 result->latest_version,
452 (method == INSTALL_METHOD_GITHUB || method == INSTALL_METHOD_UNKNOWN) ? "Download: " : "Run: ", suggestion,
453 result->release_url);
454}
int buffer_size
Size of circular buffer.
Definition grep.c:90
void update_check_get_upgrade_suggestion(install_method_t method, const char *latest_version, char *buffer, size_t buffer_size)
Get upgrade suggestion string.
install_method_t update_check_detect_install_method(void)
Detect installation method.
char latest_version[64]
Latest version tag (e.g., "v0.9.0")
char current_version[64]
Current version string.

References buffer_size, update_check_result_t::current_version, INSTALL_METHOD_GITHUB, INSTALL_METHOD_UNKNOWN, update_check_result_t::latest_version, update_check_result_t::release_url, update_check_detect_install_method(), and update_check_get_upgrade_suggestion().

Referenced by action_check_update_immediate().

◆ update_check_get_upgrade_suggestion()

void update_check_get_upgrade_suggestion ( install_method_t  method,
const char *  latest_version,
char *  buffer,
size_t  buffer_size 
)

#include <update_checker.h>

Get upgrade suggestion string.

Returns appropriate upgrade command or URL based on installation method.

Parameters
methodInstallation method
latest_versionLatest version tag for GitHub URL
[out]bufferOutput buffer for suggestion string
buffer_sizeSize of output buffer

Definition at line 393 of file update_checker.c.

394 {
395 if (!buffer || buffer_size == 0) {
396 return;
397 }
398
399 switch (method) {
401 snprintf(buffer, buffer_size, "brew upgrade ascii-chat");
402 break;
403
405 // Cache AUR helper detection to avoid spawning subprocesses on every call
406 static int aur_helper = -1; // -1 = unknown, 0 = none, 1 = paru, 2 = yay
407 if (aur_helper == -1) {
408 const char *argv_paru[] = {"paru", "--version", NULL};
409 const char *argv_yay[] = {"yay", "--version", NULL};
410 if (platform_execute_subprocess("paru", argv_paru, NULL, 0) == 0) {
411 aur_helper = 1;
412 } else if (platform_execute_subprocess("yay", argv_yay, NULL, 0) == 0) {
413 aur_helper = 2;
414 } else {
415 aur_helper = 0;
416 }
417 }
418 if (aur_helper == 1) {
419 snprintf(buffer, buffer_size, "paru -S ascii-chat");
420 } else if (aur_helper == 2) {
421 snprintf(buffer, buffer_size, "yay -S ascii-chat");
422 } else {
423 snprintf(buffer, buffer_size, "<your-aur-helper> -Ss ascii-chat");
424 }
425 break;
426 }
427
430 default:
431 snprintf(buffer, buffer_size, "https://github.com/zfogg/ascii-chat/releases/tag/%s",
432 latest_version ? latest_version : "latest");
433 break;
434 }
435}
int platform_execute_subprocess(const char *executable, const char **argv, char *output_buffer, size_t output_size)
Execute a subprocess and optionally capture its output.

References buffer_size, INSTALL_METHOD_ARCH_AUR, INSTALL_METHOD_GITHUB, INSTALL_METHOD_HOMEBREW, INSTALL_METHOD_UNKNOWN, and platform_execute_subprocess().

Referenced by update_banner_print_instructions(), update_banner_show_prompt(), and update_check_format_notification().

◆ update_check_is_cache_fresh()

bool update_check_is_cache_fresh ( const update_check_result_t *  result)

#include <update_checker.h>

Check if cache is fresh (< 7 days old)

Parameters
resultCached result to check
Returns
true if cache is < 7 days old, false otherwise

Definition at line 211 of file update_checker.c.

211 {
212 if (!result || result->last_check_time == 0) {
213 return false;
214 }
215
216 time_t now = time(NULL);
217 time_t age = now - result->last_check_time;
218
220}
time_t last_check_time
Timestamp of last check.
#define UPDATE_CHECK_CACHE_MAX_AGE_SECONDS

References update_check_result_t::last_check_time, and UPDATE_CHECK_CACHE_MAX_AGE_SECONDS.

Referenced by update_check_startup().

◆ update_check_load_cache()

asciichat_error_t update_check_load_cache ( update_check_result_t *  result)

#include <update_checker.h>

Load cached update check result.

Reads cache file from ~/.config/ascii-chat/last_update_check. Cache format (4 lines):

  • Line 1: Unix timestamp
  • Line 2: Latest version tag (or empty if check failed)
  • Line 3: Latest SHA (full 40 chars, or empty if check failed)
  • Line 4: Exact GitHub release URL
Parameters
[out]resultOutput structure with cached results
Returns
ASCIICHAT_OK if cache valid, error if missing/corrupt

Definition at line 79 of file update_checker.c.

79 {
80 if (!result) {
81 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL result pointer");
82 }
83
84 memset(result, 0, sizeof(*result));
85
86 char *cache_path = get_cache_file_path();
87 if (!cache_path) {
88 return SET_ERRNO(ERROR_FILE_OPERATION, "Could not determine cache file path");
89 }
90
91 FILE *f = platform_fopen("file_stream", cache_path, "r");
92 if (!f) {
93 const char *error_msg = file_read_error_message(cache_path);
94 SAFE_FREE(cache_path);
95 return SET_ERRNO(ERROR_FILE_OPERATION, "%s", error_msg);
96 }
97
98 // Read line 1: timestamp
99 char line[512];
100 if (!fgets(line, sizeof(line), f)) {
101 fclose(f);
102 SAFE_FREE(cache_path);
103 return SET_ERRNO(ERROR_FILE_OPERATION, "Failed to read timestamp from cache");
104 }
105
106 // Parse timestamp safely with error checking
107 errno = 0;
108 char *endptr = NULL;
109 long long timestamp_ll = strtoll(line, &endptr, 10);
110 if (errno != 0 || endptr == line) {
111 log_error("Failed to parse timestamp from cache: invalid value '%s'", line);
112 fclose(f);
113 SAFE_FREE(cache_path);
114 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid timestamp in cache file");
115 }
116 result->last_check_time = (time_t)timestamp_ll;
117
118 // Read line 2: latest version (may be empty if check failed)
119 if (fgets(line, sizeof(line), f)) {
120 trim_line_ending(line);
121 SAFE_STRNCPY(result->latest_version, line, sizeof(result->latest_version));
122 }
123
124 // Read line 3: latest SHA (may be empty if check failed)
125 if (fgets(line, sizeof(line), f)) {
126 trim_line_ending(line);
127 SAFE_STRNCPY(result->latest_sha, line, sizeof(result->latest_sha));
128 }
129
130 // Read line 4: release URL. This field is required in the current cache
131 // format; an older or partially-written cache must be refreshed.
132 if (!fgets(line, sizeof(line), f)) {
133 fclose(f);
134 // Treat a legacy cache exactly like a missing cache. Close the file before
135 // removing it so this also works on Windows, where open files cannot be
136 // deleted.
137 (void)remove(cache_path);
138 SAFE_FREE(cache_path);
139 return SET_ERRNO(ERROR_FORMAT, "Update cache format is obsolete");
140 }
141 trim_line_ending(line);
142 SAFE_STRNCPY(result->release_url, line, sizeof(result->release_url));
143
144 fclose(f);
145
146 // Fill in current version/SHA
149
150 // Determine if update is available using version comparison (if we have cached data)
151 if (result->latest_version[0] != '\0') {
152 semantic_version_t current_ver = version_parse(result->current_version);
153 semantic_version_t latest_ver = version_parse(result->latest_version);
154
155 if (current_ver.valid && latest_ver.valid) {
156 int cmp = version_compare(latest_ver, current_ver);
157 result->update_available = (cmp > 0); // Update available if latest > current
158 result->check_succeeded = true;
159 } else {
160 // Cache contains invalid version data - delete it
161 log_warn("Update cache contains invalid version data (current:%s, latest:%s) - deleting corrupted cache",
162 result->current_version, result->latest_version);
163 if (remove(cache_path) != 0) {
164 log_warn("Failed to delete corrupted cache file: %s", cache_path);
165 }
166 SAFE_FREE(cache_path);
167 return SET_ERRNO(ERROR_FORMAT, "Corrupted cache file deleted");
168 }
169 }
170
171 SAFE_FREE(cache_path);
172 return ASCIICHAT_OK;
173}
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
@ ERROR_FILE_OPERATION
@ ERROR_FORMAT
FILE * platform_fopen(const char *name, const char *filename, const char *mode)
Safe file open stream (fopen replacement)
const char * file_read_error_message(const char *path)
Get human-readable error message for file read failure.
Definition filesystem.c:24
Parsed semantic version structure.
Definition version.h:47
bool valid
True if parsing succeeded.
Definition version.h:51
char current_sha[41]
Current commit SHA.
bool update_available
True if newer version exists.
bool check_succeeded
True if check completed (even if no update)
char release_url[512]
GitHub release page URL.
char latest_sha[41]
Latest release commit SHA (full 40 chars + null)
int version_compare(semantic_version_t a, semantic_version_t b)
Compare two semantic versions.
Definition version.c:112
semantic_version_t version_parse(const char *version_string)
Parse a version string into semantic version components.
Definition version.c:49
#define ASCII_CHAT_GIT_COMMIT_HASH
Definition version.h:12

References ASCII_CHAT_GIT_COMMIT_HASH, ASCII_CHAT_VERSION_STRING, ASCIICHAT_OK, update_check_result_t::check_succeeded, update_check_result_t::current_sha, update_check_result_t::current_version, errno, ERROR_FILE_OPERATION, ERROR_FORMAT, ERROR_INVALID_PARAM, file_read_error_message(), update_check_result_t::last_check_time, update_check_result_t::latest_sha, update_check_result_t::latest_version, log_error, log_warn, platform_fopen(), update_check_result_t::release_url, SAFE_FREE, SAFE_STRNCPY, SET_ERRNO, update_check_result_t::update_available, semantic_version_t::valid, version_compare(), and version_parse().

Referenced by update_check_startup().

◆ update_check_perform()

asciichat_error_t update_check_perform ( update_check_result_t *  result)

#include <update_checker.h>

Check for updates from GitHub releases API.

Performs DNS connectivity test, fetches latest release from GitHub API, compares SHA with current build, and caches the result.

Parameters
[out]resultOutput structure with check results
Returns
ASCIICHAT_OK on success (check completed), error otherwise
Note
On timeout or network error, logs warning and marks as checked in cache
On DNS failure, does not update cache (will retry when online)
Timeout is 2 seconds for the entire operation

Definition at line 265 of file update_checker.c.

265 {
266 if (!result) {
267 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL result pointer");
268 }
269
270 memset(result, 0, sizeof(*result));
271
272 // Fill in current version and SHA
275 result->last_check_time = time(NULL);
276
277 // Test DNS connectivity first
279 log_warn("No internet connectivity detected, skipping update check");
280 // Don't update cache - we'll retry when online
281 return SET_ERRNO(ERROR_NETWORK, "DNS connectivity test failed");
282 }
283
284 // Fetch latest release from GitHub API
285 log_info("Checking for updates from GitHub releases...");
287 if (!response) {
288 log_warn("Failed to fetch GitHub releases API (timeout or network error)");
289 // Don't save cache on network failure — retry next startup
290 result->check_succeeded = false;
291 return SET_ERRNO(ERROR_NETWORK, "Failed to fetch GitHub releases");
292 }
293
294 // Parse JSON response
295 char latest_tag[64] = {0};
296 char release_url[512] = {0};
297
298 if (!parse_github_release_json(response, latest_tag, sizeof(latest_tag), release_url, sizeof(release_url))) {
299 log_error("Failed to parse GitHub API response");
300 SAFE_FREE(response);
301 // Don't save cache on parse failure — retry next startup
302 result->check_succeeded = false;
303 return SET_ERRNO(ERROR_FORMAT, "Failed to parse GitHub API JSON");
304 }
305
306 SAFE_FREE(response);
307
308 // Fill in result
309 SAFE_STRNCPY(result->latest_version, latest_tag, sizeof(result->latest_version));
310 SAFE_STRNCPY(result->release_url, release_url, sizeof(result->release_url));
311 result->latest_sha[0] = '\0';
312 result->check_succeeded = true;
313
314 // Compare versions semantically
315 semantic_version_t current_ver = version_parse(result->current_version);
316 semantic_version_t latest_ver = version_parse(result->latest_version);
317
318 if (!current_ver.valid || !latest_ver.valid) {
319 log_warn("Failed to parse version strings for comparison (current: %s, latest: %s)", result->current_version,
320 result->latest_version);
321 result->update_available = false;
322 } else {
323 int cmp = version_compare(latest_ver, current_ver);
324 result->update_available = (cmp > 0); // Update available if latest > current
325 }
326
327 if (result->update_available) {
328 log_info("Update available: %s → %s", result->current_version, result->latest_version);
329 } else {
330 log_info("Already on latest version: %s", result->current_version);
331 }
332
333 // Save to cache
335
336 return ASCIICHAT_OK;
337}
bool dns_test_connectivity(const char *hostname)
Test DNS connectivity by resolving a hostname.
Definition dns.c:11
char * https_get(const char *hostname, const char *path)
Perform HTTPS GET request.
asciichat_error_t update_check_save_cache(const update_check_result_t *result)
Save update check result to cache.
#define GITHUB_API_HOSTNAME
#define GITHUB_RELEASES_PATH

References ASCII_CHAT_GIT_COMMIT_HASH, ASCII_CHAT_VERSION_STRING, ASCIICHAT_OK, update_check_result_t::check_succeeded, update_check_result_t::current_sha, update_check_result_t::current_version, dns_test_connectivity(), ERROR_FORMAT, ERROR_INVALID_PARAM, ERROR_NETWORK, GITHUB_API_HOSTNAME, GITHUB_RELEASES_PATH, https_get(), update_check_result_t::last_check_time, update_check_result_t::latest_sha, update_check_result_t::latest_version, log_error, log_info, log_warn, update_check_result_t::release_url, SAFE_FREE, SAFE_STRNCPY, SET_ERRNO, update_check_result_t::update_available, update_check_save_cache(), semantic_version_t::valid, version_compare(), and version_parse().

Referenced by action_check_update_immediate(), and update_check_startup().

◆ update_check_save_cache()

asciichat_error_t update_check_save_cache ( const update_check_result_t *  result)

#include <update_checker.h>

Save update check result to cache.

Writes cache file to ~/.config/ascii-chat/last_update_check.

Parameters
resultResult to cache
Returns
ASCIICHAT_OK on success, error otherwise

Definition at line 175 of file update_checker.c.

175 {
176 if (!result) {
177 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL result pointer");
178 }
179
180 char *cache_path = get_cache_file_path();
181 if (!cache_path) {
182 return SET_ERRNO(ERROR_FILE_OPERATION, "Could not determine cache file path");
183 }
184
185 FILE *f = platform_fopen("file_stream", cache_path, "w");
186 if (!f) {
187 const char *error_msg = file_write_error_message(cache_path);
188 SAFE_FREE(cache_path);
189 return SET_ERRNO(ERROR_FILE_OPERATION, "%s", error_msg);
190 }
191
192 // Write timestamp
193 fprintf(f, "%lld\n", (long long)result->last_check_time);
194
195 // Write version (may be empty if check failed)
196 fprintf(f, "%s\n", result->latest_version);
197
198 // Write SHA (may be empty if check failed)
199 fprintf(f, "%s\n", result->latest_sha);
200
201 // Write the release URL so cached notifications retain the exact GitHub
202 // source returned by the API.
203 fprintf(f, "%s\n", result->release_url);
204
205 fclose(f);
206 SAFE_FREE(cache_path);
207
208 return ASCIICHAT_OK;
209}
const char * file_write_error_message(const char *path)
Get human-readable error message for file write failure.
Definition filesystem.c:43

References ASCIICHAT_OK, ERROR_FILE_OPERATION, ERROR_INVALID_PARAM, file_write_error_message(), update_check_result_t::last_check_time, update_check_result_t::latest_sha, update_check_result_t::latest_version, platform_fopen(), update_check_result_t::release_url, SAFE_FREE, and SET_ERRNO.

Referenced by update_check_perform().

◆ update_check_startup()

asciichat_error_t update_check_startup ( update_check_result_t *  result)

#include <update_checker.h>

Perform startup update check with caching.

Designed for automatic startup checks. Uses cache if fresh (< 7 days), otherwise performs fresh check. Non-blocking and silent on failure.

Parameters
[out]resultOutput structure with check results (can be NULL if you only want logging)
Returns
ASCIICHAT_OK if check succeeded, error otherwise
Note
Does nothing if cache is fresh
Logs warning on network failure but does not fail
Updates cache only if check succeeds

Definition at line 456 of file update_checker.c.

456 {
457 update_check_result_t local_result;
458 update_check_result_t *target = result ? result : &local_result;
459
460 // Try to load from cache first
461 asciichat_error_t cache_err = update_check_load_cache(target);
462 if (cache_err == ASCIICHAT_OK && update_check_is_cache_fresh(target)) {
463 // Cache is fresh, use it
464 log_debug("Using cached update check result (age: %.1f days)",
465 (time(NULL) - target->last_check_time) / (double)SEC_PER_DAY);
466 return ASCIICHAT_OK;
467 }
468
469 // Cache is stale or missing, perform fresh check
470 log_debug("Performing automatic update check (cache %s)", cache_err == ASCIICHAT_OK ? "stale" : "missing");
471 asciichat_error_t check_err = update_check_perform(target);
472 if (check_err != ASCIICHAT_OK) {
473 // Check failed, but don't fail startup
474 log_debug("Automatic update check failed (continuing startup)");
475 return check_err;
476 }
477
478 return ASCIICHAT_OK;
479}
#define SEC_PER_DAY
Definition time.h:169
bool update_check_is_cache_fresh(const update_check_result_t *result)
Check if cache is fresh (< 7 days old)
asciichat_error_t update_check_perform(update_check_result_t *result)
Check for updates from GitHub releases API.
asciichat_error_t update_check_load_cache(update_check_result_t *result)
Load cached update check result.
Update check result data.

References ASCIICHAT_OK, update_check_result_t::last_check_time, log_debug, SEC_PER_DAY, update_check_is_cache_fresh(), update_check_load_cache(), and update_check_perform().