Line data Source code
1 : /**
2 : * @file socket.c
3 : * @brief Mock socket implementation for unit/integration testing.
4 : *
5 : * Provides an in-memory fake server that stores sent data and returns
6 : * pre-programmed responses. Test accessors in mock_socket.h.
7 : */
8 :
9 : #include "platform/socket.h"
10 :
11 : #include <errno.h>
12 : #include <stdlib.h>
13 : #include <string.h>
14 :
15 : /* ---- Internal mock state ---- */
16 :
17 : #define MOCK_BUF_SIZE (256 * 1024)
18 :
19 : static struct {
20 : int created;
21 : int connected;
22 : int closed;
23 : int nonblocking;
24 :
25 : /* Sent data (client → server) */
26 : uint8_t *sent;
27 : size_t sent_len;
28 : size_t sent_cap;
29 :
30 : /* Pre-programmed response (server → client) */
31 : uint8_t *response;
32 : size_t response_len;
33 : size_t response_pos;
34 :
35 : /* Failure injection */
36 : int fail_create;
37 : int fail_connect;
38 : int refuse_connect; /* persistent ECONNREFUSED */
39 : int fail_send_at; /* 0 = never */
40 : int fail_recv_at;
41 : int short_send_at;
42 : int eintr_send_at; /* Nth send() returns -1/EINTR, then succeeds */
43 : int eintr_recv_at; /* Nth recv() returns -1/EINTR, then succeeds */
44 : int send_call_n; /* call counters */
45 : int recv_call_n;
46 :
47 : /* TEST-82: fragmentation + one-shot EINTR/EAGAIN/EOF */
48 : size_t send_fragment; /* cap each send() at N bytes; 0 = off */
49 : size_t recv_fragment; /* cap each recv() at N bytes; 0 = off */
50 : int inject_eintr_send; /* next send() returns -1/EINTR */
51 : int inject_eintr_recv; /* next recv() returns -1/EINTR */
52 : int inject_eagain_send; /* next send() returns -1/EAGAIN */
53 : int inject_eagain_recv; /* next recv() returns -1/EAGAIN */
54 : int kill_next_recv; /* next recv() returns 0 (EOF) */
55 :
56 : /* Emulator hook: runs after every successful send, lets the fake
57 : * server parse + reply before the next recv. */
58 : void (*on_sent)(const uint8_t *, size_t);
59 : } g_mock_socket;
60 :
61 : /* ---- Test accessor functions ---- */
62 :
63 475 : void mock_socket_reset(void) {
64 475 : free(g_mock_socket.sent);
65 475 : free(g_mock_socket.response);
66 475 : memset(&g_mock_socket, 0, sizeof(g_mock_socket));
67 475 : }
68 :
69 0 : int mock_socket_was_created(void) { return g_mock_socket.created; }
70 0 : int mock_socket_was_connected(void) { return g_mock_socket.connected; }
71 0 : int mock_socket_was_closed(void) { return g_mock_socket.closed; }
72 :
73 0 : const uint8_t *mock_socket_get_sent(size_t *out_len) {
74 0 : if (out_len) *out_len = g_mock_socket.sent_len;
75 0 : return g_mock_socket.sent;
76 : }
77 :
78 4 : void mock_socket_set_response(const uint8_t *data, size_t len) {
79 4 : free(g_mock_socket.response);
80 4 : g_mock_socket.response = (uint8_t *)malloc(len);
81 4 : memcpy(g_mock_socket.response, data, len);
82 4 : g_mock_socket.response_len = len;
83 4 : g_mock_socket.response_pos = 0;
84 4 : }
85 :
86 682 : void mock_socket_append_response(const uint8_t *data, size_t len) {
87 682 : if (!data || len == 0) return;
88 682 : size_t new_len = g_mock_socket.response_len + len;
89 682 : g_mock_socket.response = (uint8_t *)realloc(g_mock_socket.response, new_len);
90 682 : memcpy(g_mock_socket.response + g_mock_socket.response_len, data, len);
91 682 : g_mock_socket.response_len = new_len;
92 : }
93 :
94 0 : void mock_socket_clear_sent(void) {
95 0 : g_mock_socket.sent_len = 0;
96 0 : }
97 :
98 1 : void mock_socket_fail_create(void) { g_mock_socket.fail_create = 1; }
99 1 : void mock_socket_fail_connect(void) { g_mock_socket.fail_connect = 1; }
100 4 : void mock_socket_fail_send_at(int n) { g_mock_socket.fail_send_at = n; }
101 0 : void mock_socket_fail_recv_at(int n) { g_mock_socket.fail_recv_at = n; }
102 0 : void mock_socket_short_send_at(int n) { g_mock_socket.short_send_at = n; }
103 0 : void mock_socket_eintr_send_at(int n) { g_mock_socket.eintr_send_at = n; }
104 0 : void mock_socket_eintr_recv_at(int n) { g_mock_socket.eintr_recv_at = n; }
105 :
106 1 : void mock_socket_set_send_fragment(size_t step) { g_mock_socket.send_fragment = step; }
107 1 : void mock_socket_set_recv_fragment(size_t step) { g_mock_socket.recv_fragment = step; }
108 1 : void mock_socket_inject_eintr_next_send(void) { g_mock_socket.inject_eintr_send = 1; }
109 1 : void mock_socket_inject_eintr_next_recv(void) { g_mock_socket.inject_eintr_recv = 1; }
110 1 : void mock_socket_inject_eagain_next_send(void) { g_mock_socket.inject_eagain_send = 1; }
111 1 : void mock_socket_inject_eagain_next_recv(void) { g_mock_socket.inject_eagain_recv = 1; }
112 1 : void mock_socket_kill_on_next_recv(void) { g_mock_socket.kill_next_recv = 1; }
113 1 : void mock_socket_refuse_connect(void) { g_mock_socket.refuse_connect = 1; }
114 :
115 950 : void mock_socket_set_on_sent(void (*fn)(const uint8_t *, size_t)) {
116 950 : g_mock_socket.on_sent = fn;
117 950 : }
118 :
119 : /* ---- socket.h interface implementation ---- */
120 :
121 259 : int sys_socket_create(void) {
122 259 : if (g_mock_socket.fail_create) {
123 1 : g_mock_socket.fail_create = 0;
124 1 : return -1;
125 : }
126 258 : g_mock_socket.created++;
127 258 : return 42; /* fake fd */
128 : }
129 :
130 258 : int sys_socket_connect(int fd, const char *host, int port) {
131 : (void)fd; (void)host; (void)port;
132 258 : if (g_mock_socket.refuse_connect) {
133 : /* Persistent refusal — every reconnect attempt fails with
134 : * ECONNREFUSED until mock_socket_reset() clears it. */
135 2 : errno = ECONNREFUSED;
136 2 : return -1;
137 : }
138 256 : if (g_mock_socket.fail_connect) {
139 1 : g_mock_socket.fail_connect = 0;
140 1 : return -1;
141 : }
142 255 : g_mock_socket.connected++;
143 255 : return 0;
144 : }
145 :
146 922 : ssize_t sys_socket_send(int fd, const void *buf, size_t len) {
147 : (void)fd;
148 922 : if (!buf || len == 0) return 0;
149 :
150 922 : g_mock_socket.send_call_n++;
151 :
152 : /* Next-call EINTR / EAGAIN injection beats the Nth-call variant —
153 : * tests that do not want to count calls can use these one-shots. */
154 922 : if (g_mock_socket.inject_eintr_send) {
155 1 : g_mock_socket.inject_eintr_send = 0;
156 1 : errno = EINTR;
157 1 : return -1;
158 : }
159 921 : if (g_mock_socket.inject_eagain_send) {
160 1 : g_mock_socket.inject_eagain_send = 0;
161 1 : errno = EAGAIN;
162 1 : return -1;
163 : }
164 :
165 920 : if (g_mock_socket.eintr_send_at &&
166 0 : g_mock_socket.send_call_n == g_mock_socket.eintr_send_at) {
167 0 : g_mock_socket.eintr_send_at = 0;
168 0 : errno = EINTR;
169 0 : return -1;
170 : }
171 :
172 920 : if (g_mock_socket.fail_send_at &&
173 5 : g_mock_socket.send_call_n == g_mock_socket.fail_send_at) {
174 4 : g_mock_socket.fail_send_at = 0;
175 4 : return -1;
176 : }
177 :
178 : /* send_fragment: emit at most `step` bytes per call. Distinct from
179 : * short_send_at which is one-shot — send_fragment persists so the
180 : * transport layer loops until the caller's payload is drained. */
181 916 : size_t emit = len;
182 916 : if (g_mock_socket.send_fragment && emit > g_mock_socket.send_fragment) {
183 5 : emit = g_mock_socket.send_fragment;
184 : }
185 :
186 : /* Append to sent buffer */
187 916 : if (g_mock_socket.sent_len + emit > g_mock_socket.sent_cap) {
188 246 : size_t new_cap = g_mock_socket.sent_cap ? g_mock_socket.sent_cap : 4096;
189 267 : while (new_cap < g_mock_socket.sent_len + emit) new_cap *= 2;
190 246 : g_mock_socket.sent = (uint8_t *)realloc(g_mock_socket.sent, new_cap);
191 246 : g_mock_socket.sent_cap = new_cap;
192 : }
193 916 : memcpy(g_mock_socket.sent + g_mock_socket.sent_len, buf, emit);
194 916 : g_mock_socket.sent_len += emit;
195 :
196 916 : if (g_mock_socket.short_send_at &&
197 0 : g_mock_socket.send_call_n == g_mock_socket.short_send_at && emit > 1) {
198 0 : g_mock_socket.short_send_at = 0;
199 : /* Hook fires after short send too so the server can react to
200 : * the partial frame exactly as the real DC would. */
201 0 : if (g_mock_socket.on_sent) {
202 0 : g_mock_socket.on_sent(g_mock_socket.sent, g_mock_socket.sent_len);
203 : }
204 0 : return (ssize_t)(emit - 1);
205 : }
206 :
207 916 : if (g_mock_socket.on_sent) {
208 916 : g_mock_socket.on_sent(g_mock_socket.sent, g_mock_socket.sent_len);
209 : }
210 916 : return (ssize_t)emit;
211 : }
212 :
213 711 : ssize_t sys_socket_recv(int fd, void *buf, size_t len) {
214 : (void)fd;
215 711 : if (!buf || len == 0) return 0;
216 :
217 711 : g_mock_socket.recv_call_n++;
218 :
219 : /* Kill-on-next wins unconditionally so mid-RPC disconnect tests can
220 : * interleave it with queued responses. We also flush any pending
221 : * response bytes: a real peer that closed the socket before the
222 : * reply was written never delivers those bytes, so the client must
223 : * reconnect and re-issue the RPC rather than finding the old reply
224 : * on the next recv. */
225 711 : if (g_mock_socket.kill_next_recv) {
226 1 : g_mock_socket.kill_next_recv = 0;
227 1 : g_mock_socket.response_pos = g_mock_socket.response_len;
228 1 : return 0;
229 : }
230 :
231 710 : if (g_mock_socket.inject_eintr_recv) {
232 1 : g_mock_socket.inject_eintr_recv = 0;
233 1 : errno = EINTR;
234 1 : return -1;
235 : }
236 709 : if (g_mock_socket.inject_eagain_recv) {
237 1 : g_mock_socket.inject_eagain_recv = 0;
238 1 : errno = EAGAIN;
239 1 : return -1;
240 : }
241 :
242 708 : if (g_mock_socket.eintr_recv_at &&
243 0 : g_mock_socket.recv_call_n == g_mock_socket.eintr_recv_at) {
244 0 : g_mock_socket.eintr_recv_at = 0;
245 0 : errno = EINTR;
246 0 : return -1;
247 : }
248 :
249 708 : if (g_mock_socket.fail_recv_at &&
250 0 : g_mock_socket.recv_call_n == g_mock_socket.fail_recv_at) {
251 0 : g_mock_socket.fail_recv_at = 0;
252 0 : return -1;
253 : }
254 :
255 708 : size_t avail = g_mock_socket.response_len - g_mock_socket.response_pos;
256 708 : if (avail == 0) return 0; /* EOF */
257 :
258 705 : size_t to_read = len < avail ? len : avail;
259 705 : if (g_mock_socket.recv_fragment && to_read > g_mock_socket.recv_fragment) {
260 6 : to_read = g_mock_socket.recv_fragment;
261 : }
262 705 : memcpy(buf, g_mock_socket.response + g_mock_socket.response_pos, to_read);
263 705 : g_mock_socket.response_pos += to_read;
264 :
265 705 : return (ssize_t)to_read;
266 : }
267 :
268 258 : int sys_socket_close(int fd) {
269 : (void)fd;
270 258 : g_mock_socket.closed++;
271 258 : return 0;
272 : }
273 :
274 0 : int sys_socket_set_nonblocking(int fd) {
275 : (void)fd;
276 0 : g_mock_socket.nonblocking++;
277 0 : return 0;
278 : }
|