ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
tcp/transport.c
Go to the documentation of this file.
1
12#include <ascii-chat/network/acip/transport.h>
13#include <ascii-chat/network/packet.h>
14#include <ascii-chat/network/network.h>
15#include <ascii-chat/log/logging.h>
16#include <ascii-chat/platform/socket.h>
17#include <ascii-chat/crypto/crypto.h>
18#include <ascii-chat/buffer_pool.h>
19#include <ascii-chat/util/endian.h>
20#include <ascii-chat/network/crc32.h>
21#include <string.h>
22
30
31// =============================================================================
32// Helper Functions
33// =============================================================================
34
38static asciichat_error_t tcp_send_all(socket_t sockfd, const void *data, size_t len) {
39 const uint8_t *ptr = (const uint8_t *)data;
40 size_t remaining = len;
41 size_t total_sent = 0;
42
43 log_debug("★ TCP_SEND_ALL: sockfd=%d, len=%zu", sockfd, len);
44
45 while (remaining > 0) {
46 ssize_t sent = socket_send(sockfd, ptr, remaining, 0);
47 if (sent < 0) {
48 log_error("★ TCP_SEND_ALL: socket_send failed at offset %zu/%zu", total_sent, len);
49 return SET_ERRNO_SYS(ERROR_NETWORK,
50 "Socket send failed (tried to send %zu bytes, %zu remaining, already sent %zu)", len,
51 remaining, total_sent);
52 }
53 if (sent == 0) {
54 log_error("★ TCP_SEND_ALL: socket closed at offset %zu/%zu", total_sent, len);
55 return SET_ERRNO(ERROR_NETWORK, "Socket closed (tried to send %zu bytes, %zu remaining, already sent %zu)", len,
56 remaining, total_sent);
57 }
58 ptr += sent;
59 remaining -= (size_t)sent;
60 total_sent += (size_t)sent;
61 log_debug("★ TCP_SEND_ALL: sent %zd bytes, total=%zu/%zu, remaining=%zu", sent, total_sent, len, remaining);
62 }
63
64 log_debug("★ TCP_SEND_ALL: SUCCESS - sent all %zu bytes", len);
65 return ASCIICHAT_OK;
66}
67
68// =============================================================================
69// TCP Transport Methods
70// =============================================================================
71
72static asciichat_error_t tcp_send(acip_transport_t *transport, const void *data, size_t len) {
73 tcp_transport_data_t *tcp = (tcp_transport_data_t *)transport->impl_data;
74
75 if (!tcp->is_connected) {
76 return SET_ERRNO(ERROR_NETWORK, "TCP transport not connected");
77 }
78
79 // Data already has packet header from send.c, so we need to extract the type
80 // to determine if this is a handshake packet (which should NOT be encrypted)
81 if (len < sizeof(packet_header_t)) {
82 return SET_ERRNO(ERROR_NETWORK, "Packet too small: %zu < %zu", len, sizeof(packet_header_t));
83 }
84
85 // Extract packet type from header
86 const packet_header_t *header = (const packet_header_t *)data;
87 uint16_t packet_type = NET_TO_HOST_U16(header->type);
88
89 // Check if encryption is needed
90 bool should_encrypt = false;
91 if (transport->crypto_ctx && transport->crypto_ctx->encrypt_data && crypto_is_ready(transport->crypto_ctx)) {
92 // Handshake packets are ALWAYS sent unencrypted
93 if (!packet_is_handshake_type((packet_type_t)packet_type)) {
94 should_encrypt = true;
95 }
96 }
97
98 // If no encryption needed, send raw data
99 if (!should_encrypt) {
100 return tcp_send_all(tcp->sockfd, data, len);
101 }
102
103 // Encrypt the entire packet (header + payload)
104 size_t ciphertext_size = len + CRYPTO_NONCE_SIZE + CRYPTO_MAC_SIZE;
105 uint8_t *ciphertext = buffer_pool_alloc(NULL, ciphertext_size);
106 if (!ciphertext) {
107 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate ciphertext buffer");
108 }
109
110 size_t ciphertext_len;
111 crypto_result_t result =
112 crypto_encrypt(transport->crypto_ctx, data, len, ciphertext, ciphertext_size, &ciphertext_len);
113 if (result != CRYPTO_OK) {
114 buffer_pool_free(NULL, ciphertext, ciphertext_size);
115 return SET_ERRNO(ERROR_CRYPTO, "Failed to encrypt packet: %s", crypto_result_to_string(result));
116 }
117
118 // Build PACKET_TYPE_ENCRYPTED header
119 packet_header_t encrypted_header;
120 encrypted_header.magic = HOST_TO_NET_U64(PACKET_MAGIC);
121 encrypted_header.type = HOST_TO_NET_U16(PACKET_TYPE_ENCRYPTED);
122 encrypted_header.length = HOST_TO_NET_U32((uint32_t)ciphertext_len);
123 encrypted_header.crc32 = HOST_TO_NET_U32(asciichat_crc32(ciphertext, ciphertext_len));
124 encrypted_header.client_id = 0;
125
126 // Send encrypted packet: header + ciphertext
127 asciichat_error_t send_result = ASCIICHAT_OK;
128 send_result = tcp_send_all(tcp->sockfd, &encrypted_header, sizeof(encrypted_header));
129 if (send_result == ASCIICHAT_OK) {
130 send_result = tcp_send_all(tcp->sockfd, ciphertext, ciphertext_len);
131 }
132
133 buffer_pool_free(NULL, ciphertext, ciphertext_size);
134
135 log_debug_every(LOG_RATE_SLOW, "Sent encrypted packet (original type %d as PACKET_TYPE_ENCRYPTED)", packet_type);
136 return send_result;
137}
138
139static asciichat_error_t tcp_recv(acip_transport_t *transport, void **buffer, size_t *out_len,
140 void **out_allocated_buffer) {
141 tcp_transport_data_t *tcp = (tcp_transport_data_t *)transport->impl_data;
142
143 if (!tcp->is_connected) {
144 return SET_ERRNO(ERROR_NETWORK, "TCP transport not connected");
145 }
146
147 // Use secure packet receive with envelope
148 packet_envelope_t envelope;
149 bool enforce_encryption = (transport->crypto_ctx != NULL && transport->crypto_ctx->encrypt_data);
150 packet_recv_result_t result =
151 receive_packet_secure(tcp->sockfd, transport->crypto_ctx, enforce_encryption, &envelope);
152
153 if (result != PACKET_RECV_SUCCESS) {
154 if (result == PACKET_RECV_EOF) {
155 return SET_ERRNO(ERROR_NETWORK, "Connection closed");
156 } else if (result == PACKET_RECV_SECURITY_VIOLATION) {
157 return SET_ERRNO(ERROR_CRYPTO, "Security violation");
158 } else {
159 return SET_ERRNO(ERROR_NETWORK, "Failed to receive packet");
160 }
161 }
162
163 *buffer = envelope.data;
164 *out_len = envelope.len;
165 *out_allocated_buffer = envelope.allocated_buffer;
166
167 return ASCIICHAT_OK;
168}
169
170static asciichat_error_t tcp_close(acip_transport_t *transport) {
171 tcp_transport_data_t *tcp = (tcp_transport_data_t *)transport->impl_data;
172
173 if (!tcp->is_connected) {
174 return ASCIICHAT_OK; // Already closed
175 }
176
177 // Note: We do NOT close the socket - caller owns it
178 // We just mark ourselves as disconnected
179 tcp->is_connected = false;
180
181 log_debug("TCP transport marked as disconnected (socket not closed)");
182 return ASCIICHAT_OK;
183}
184
185static acip_transport_type_t tcp_get_type(acip_transport_t *transport) {
186 (void)transport;
187 return ACIP_TRANSPORT_TCP;
188}
189
190static socket_t tcp_get_socket(acip_transport_t *transport) {
191 tcp_transport_data_t *tcp = (tcp_transport_data_t *)transport->impl_data;
192 return tcp->sockfd;
193}
194
195static bool tcp_is_connected(acip_transport_t *transport) {
196 tcp_transport_data_t *tcp = (tcp_transport_data_t *)transport->impl_data;
197 return tcp->is_connected;
198}
199
200// =============================================================================
201// TCP Transport Method Table
202// =============================================================================
203
204static const acip_transport_methods_t tcp_methods = {
205 .send = tcp_send,
206 .recv = tcp_recv,
207 .close = tcp_close,
208 .get_type = tcp_get_type,
209 .get_socket = tcp_get_socket,
210 .is_connected = tcp_is_connected,
211 .destroy_impl = NULL, // No custom cleanup needed
212};
213
214// =============================================================================
215// TCP Transport Creation
216// =============================================================================
217
218acip_transport_t *acip_tcp_transport_create(socket_t sockfd, crypto_context_t *crypto_ctx) {
219 if (sockfd == INVALID_SOCKET_VALUE) {
220 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid socket descriptor");
221 return NULL;
222 }
223
224 // Allocate transport structure
225 acip_transport_t *transport = SAFE_MALLOC(sizeof(acip_transport_t), acip_transport_t *);
226 if (!transport) {
227 SET_ERRNO(ERROR_MEMORY, "Failed to allocate TCP transport");
228 return NULL;
229 }
230
231 // Allocate TCP-specific data
232 tcp_transport_data_t *tcp_data = SAFE_MALLOC(sizeof(tcp_transport_data_t), tcp_transport_data_t *);
233 if (!tcp_data) {
234 SAFE_FREE(transport);
235 SET_ERRNO(ERROR_MEMORY, "Failed to allocate TCP transport data");
236 return NULL;
237 }
238
239 // Initialize TCP data
240 tcp_data->sockfd = sockfd;
241 tcp_data->is_connected = true;
242
243 // Enable TCP_NODELAY to disable Nagle's algorithm
244 // This ensures small packets are sent immediately instead of being buffered
245 int nodelay = 1;
246 if (setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (const char *)&nodelay, sizeof(nodelay)) < 0) {
247 log_warn("Failed to set TCP_NODELAY on socket %d: %s", sockfd, SAFE_STRERROR(errno));
248 // Continue anyway - this is not fatal
249 }
250
251 // Initialize transport
252 transport->methods = &tcp_methods;
253 transport->crypto_ctx = crypto_ctx;
254 transport->impl_data = tcp_data;
255
256 log_debug("Created TCP transport for socket %d (crypto: %s)", sockfd, crypto_ctx ? "enabled" : "disabled");
257
258 return transport;
259}
260
261// =============================================================================
262// Transport Destroy (shared implementation)
263// =============================================================================
264
265void acip_transport_destroy(acip_transport_t *transport) {
266 if (!transport) {
267 return;
268 }
269
270 // Close if still connected
271 if (transport->methods && transport->methods->close && transport->methods->is_connected &&
272 transport->methods->is_connected(transport)) {
273 transport->methods->close(transport);
274 }
275
276 // Call custom destroy implementation if provided
277 if (transport->methods && transport->methods->destroy_impl) {
278 transport->methods->destroy_impl(transport);
279 }
280
281 // Free implementation data
282 if (transport->impl_data) {
283 SAFE_FREE(transport->impl_data);
284 }
285
286 // Free transport structure
287 SAFE_FREE(transport);
288
289 log_debug("Destroyed ACIP transport");
290}
void buffer_pool_free(buffer_pool_t *pool, void *data, size_t size)
void * buffer_pool_alloc(buffer_pool_t *pool, size_t size)
Definition buffer_pool.c:99
int socket_t
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)
const char * crypto_result_to_string(crypto_result_t result)
bool crypto_is_ready(const crypto_context_t *ctx)
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.
Definition packet.c:566
TCP transport implementation data.
socket_t sockfd
Socket descriptor (NOT owned - don't close)
bool is_connected
Connection state.
acip_transport_t * acip_tcp_transport_create(socket_t sockfd, crypto_context_t *crypto_ctx)
void acip_transport_destroy(acip_transport_t *transport)