Coverage Report

Created: 2026-02-23 20:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/Users/alexjokela/projects/lattice/src/net.c
Line
Count
Source
1
#include "net.h"
2
#include <string.h>
3
#include <stdlib.h>
4
#include <stdio.h>
5
6
#ifndef __EMSCRIPTEN__
7
8
#include <sys/socket.h>
9
#include <sys/select.h>
10
#include <netinet/in.h>
11
#include <arpa/inet.h>
12
#include <netdb.h>
13
#include <unistd.h>
14
#include <signal.h>
15
#include <errno.h>
16
17
/* ── Socket tracking ── */
18
19
#ifndef FD_SETSIZE
20
#define FD_SETSIZE 1024
21
#endif
22
23
static bool tracked_sockets[FD_SETSIZE];
24
static bool sigpipe_suppressed = false;
25
26
18
static void ensure_sigpipe_suppressed(void) {
27
18
    if (!sigpipe_suppressed) {
28
3
        signal(SIGPIPE, SIG_IGN);
29
3
        sigpipe_suppressed = true;
30
3
    }
31
18
}
32
33
24
static void track_socket(int fd) {
34
24
    if (fd >= 0 && fd < FD_SETSIZE)
35
24
        tracked_sockets[fd] = true;
36
24
}
37
38
24
static void untrack_socket(int fd) {
39
24
    if (fd >= 0 && fd < FD_SETSIZE)
40
24
        tracked_sockets[fd] = false;
41
24
}
42
43
45
static bool is_tracked(int fd) {
44
45
    return fd >= 0 && fd < FD_SETSIZE && tracked_sockets[fd];
45
45
}
46
47
0
static char *make_err(const char *prefix) {
48
0
    char buf[512];
49
0
    snprintf(buf, sizeof(buf), "%s: %s", prefix, strerror(errno));
50
0
    return strdup(buf);
51
0
}
52
53
/* ── tcp_listen ── */
54
55
15
int net_tcp_listen(const char *host, int port, char **err) {
56
15
    ensure_sigpipe_suppressed();
57
58
15
    int fd = socket(AF_INET, SOCK_STREAM, 0);
59
15
    if (fd < 0) { *err = make_err("tcp_listen: socket"); return -1; }
60
61
15
    int opt = 1;
62
15
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
63
64
15
    struct sockaddr_in addr;
65
15
    memset(&addr, 0, sizeof(addr));
66
15
    addr.sin_family = AF_INET;
67
15
    addr.sin_port = htons((uint16_t)port);
68
69
15
    if (inet_pton(AF_INET, host, &addr.sin_addr) != 1) {
70
0
        close(fd);
71
0
        *err = strdup("tcp_listen: invalid host address");
72
0
        return -1;
73
0
    }
74
75
15
    if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
76
0
        *err = make_err("tcp_listen: bind");
77
0
        close(fd);
78
0
        return -1;
79
0
    }
80
81
15
    if (listen(fd, SOMAXCONN) < 0) {
82
0
        *err = make_err("tcp_listen: listen");
83
0
        close(fd);
84
0
        return -1;
85
0
    }
86
87
15
    track_socket(fd);
88
15
    return fd;
89
15
}
90
91
/* ── tcp_accept ── */
92
93
9
int net_tcp_accept(int server_fd, char **err) {
94
9
    if (!is_tracked(server_fd)) {
95
3
        *err = strdup("tcp_accept: not a tracked socket");
96
3
        return -1;
97
3
    }
98
99
6
    struct sockaddr_in client_addr;
100
6
    socklen_t addr_len = sizeof(client_addr);
101
6
    int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
102
6
    if (client_fd < 0) {
103
0
        *err = make_err("tcp_accept: accept");
104
0
        return -1;
105
0
    }
106
107
6
    track_socket(client_fd);
108
6
    return client_fd;
109
6
}
110
111
/* ── tcp_connect ── */
112
113
3
int net_tcp_connect(const char *host, int port, char **err) {
114
3
    ensure_sigpipe_suppressed();
115
116
3
    struct addrinfo hints, *res;
117
3
    memset(&hints, 0, sizeof(hints));
118
3
    hints.ai_family = AF_INET;
119
3
    hints.ai_socktype = SOCK_STREAM;
120
121
3
    char port_str[16];
122
3
    snprintf(port_str, sizeof(port_str), "%d", port);
123
124
3
    int gai = getaddrinfo(host, port_str, &hints, &res);
125
3
    if (gai != 0) {
126
0
        char buf[256];
127
0
        snprintf(buf, sizeof(buf), "tcp_connect: %s", gai_strerror(gai));
128
0
        *err = strdup(buf);
129
0
        return -1;
130
0
    }
131
132
3
    int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
133
3
    if (fd < 0) {
134
0
        *err = make_err("tcp_connect: socket");
135
0
        freeaddrinfo(res);
136
0
        return -1;
137
0
    }
138
139
3
    if (connect(fd, res->ai_addr, res->ai_addrlen) < 0) {
140
0
        *err = make_err("tcp_connect: connect");
141
0
        close(fd);
142
0
        freeaddrinfo(res);
143
0
        return -1;
144
0
    }
145
146
3
    freeaddrinfo(res);
147
3
    track_socket(fd);
148
3
    return fd;
149
3
}
150
151
/* ── tcp_read ── */
152
153
6
#define TCP_READ_BUF 8192
154
155
6
char *net_tcp_read(int fd, char **err) {
156
6
    if (!is_tracked(fd)) {
157
3
        *err = strdup("tcp_read: not a tracked socket");
158
3
        return NULL;
159
3
    }
160
161
3
    char *buf = malloc(TCP_READ_BUF + 1);
162
3
    if (!buf) { *err = strdup("tcp_read: out of memory"); return NULL; }
163
164
3
    ssize_t n = recv(fd, buf, TCP_READ_BUF, 0);
165
3
    if (n < 0) {
166
0
        free(buf);
167
0
        *err = make_err("tcp_read: recv");
168
0
        return NULL;
169
0
    }
170
171
3
    buf[n] = '\0';
172
3
    return buf;
173
3
}
174
175
/* ── tcp_read_bytes ── */
176
177
0
char *net_tcp_read_bytes(int fd, size_t count, char **err) {
178
0
    if (!is_tracked(fd)) {
179
0
        *err = strdup("tcp_read_bytes: not a tracked socket");
180
0
        return NULL;
181
0
    }
182
183
0
    char *buf = malloc(count + 1);
184
0
    if (!buf) { *err = strdup("tcp_read_bytes: out of memory"); return NULL; }
185
186
0
    size_t total = 0;
187
0
    while (total < count) {
188
0
        ssize_t n = recv(fd, buf + total, count - total, 0);
189
0
        if (n <= 0) break;  /* EOF or error */
190
0
        total += (size_t)n;
191
0
    }
192
193
0
    buf[total] = '\0';
194
0
    return buf;
195
0
}
196
197
/* ── tcp_write ── */
198
199
0
bool net_tcp_write(int fd, const char *data, size_t len, char **err) {
200
0
    if (!is_tracked(fd)) {
201
0
        *err = strdup("tcp_write: not a tracked socket");
202
0
        return false;
203
0
    }
204
205
0
    size_t total = 0;
206
0
    while (total < len) {
207
0
        ssize_t n = send(fd, data + total, len - total, 0);
208
0
        if (n < 0) {
209
0
            *err = make_err("tcp_write: send");
210
0
            return false;
211
0
        }
212
0
        total += (size_t)n;
213
0
    }
214
0
    return true;
215
0
}
216
217
/* ── tcp_close ── */
218
219
24
void net_tcp_close(int fd) {
220
24
    if (is_tracked(fd)) {
221
24
        untrack_socket(fd);
222
24
        close(fd);
223
24
    }
224
24
}
225
226
/* ── tcp_peer_addr ── */
227
228
3
char *net_tcp_peer_addr(int fd, char **err) {
229
3
    if (!is_tracked(fd)) {
230
0
        *err = strdup("tcp_peer_addr: not a tracked socket");
231
0
        return NULL;
232
0
    }
233
234
3
    struct sockaddr_in addr;
235
3
    socklen_t addr_len = sizeof(addr);
236
3
    if (getpeername(fd, (struct sockaddr *)&addr, &addr_len) < 0) {
237
0
        *err = make_err("tcp_peer_addr: getpeername");
238
0
        return NULL;
239
0
    }
240
241
3
    char ip[INET_ADDRSTRLEN];
242
3
    inet_ntop(AF_INET, &addr.sin_addr, ip, sizeof(ip));
243
244
3
    char buf[64];
245
3
    snprintf(buf, sizeof(buf), "%s:%d", ip, ntohs(addr.sin_port));
246
3
    return strdup(buf);
247
3
}
248
249
/* ── tcp_set_timeout ── */
250
251
3
bool net_tcp_set_timeout(int fd, int secs, char **err) {
252
3
    if (!is_tracked(fd)) {
253
0
        *err = strdup("tcp_set_timeout: not a tracked socket");
254
0
        return false;
255
0
    }
256
257
3
    struct timeval tv;
258
3
    tv.tv_sec = secs;
259
3
    tv.tv_usec = 0;
260
261
3
    if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
262
0
        *err = make_err("tcp_set_timeout: SO_RCVTIMEO");
263
0
        return false;
264
0
    }
265
3
    if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0) {
266
0
        *err = make_err("tcp_set_timeout: SO_SNDTIMEO");
267
0
        return false;
268
0
    }
269
3
    return true;
270
3
}
271
272
#else /* __EMSCRIPTEN__ */
273
274
/* ── WASM stubs ── */
275
276
static char *wasm_err(void) {
277
    return strdup("networking not available in WASM");
278
}
279
280
int net_tcp_listen(const char *host, int port, char **err) {
281
    (void)host; (void)port;
282
    *err = wasm_err();
283
    return -1;
284
}
285
286
int net_tcp_accept(int server_fd, char **err) {
287
    (void)server_fd;
288
    *err = wasm_err();
289
    return -1;
290
}
291
292
int net_tcp_connect(const char *host, int port, char **err) {
293
    (void)host; (void)port;
294
    *err = wasm_err();
295
    return -1;
296
}
297
298
char *net_tcp_read(int fd, char **err) {
299
    (void)fd;
300
    *err = wasm_err();
301
    return NULL;
302
}
303
304
char *net_tcp_read_bytes(int fd, size_t count, char **err) {
305
    (void)fd; (void)count;
306
    *err = wasm_err();
307
    return NULL;
308
}
309
310
bool net_tcp_write(int fd, const char *data, size_t len, char **err) {
311
    (void)fd; (void)data; (void)len;
312
    *err = wasm_err();
313
    return false;
314
}
315
316
void net_tcp_close(int fd) {
317
    (void)fd;
318
}
319
320
char *net_tcp_peer_addr(int fd, char **err) {
321
    (void)fd;
322
    *err = wasm_err();
323
    return NULL;
324
}
325
326
bool net_tcp_set_timeout(int fd, int secs, char **err) {
327
    (void)fd; (void)secs;
328
    *err = wasm_err();
329
    return false;
330
}
331
332
#endif /* __EMSCRIPTEN__ */