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 1106 : void mock_socket_reset(void) {
64 1106 : free(g_mock_socket.sent);
65 1106 : free(g_mock_socket.response);
66 1106 : memset(&g_mock_socket, 0, sizeof(g_mock_socket));
67 1106 : }
68 :
69 5 : int mock_socket_was_created(void) { return g_mock_socket.created; }
70 2 : int mock_socket_was_connected(void) { return g_mock_socket.connected; }
71 3 : int mock_socket_was_closed(void) { return g_mock_socket.closed; }
72 :
73 20 : const uint8_t *mock_socket_get_sent(size_t *out_len) {
74 20 : if (out_len) *out_len = g_mock_socket.sent_len;
75 20 : return g_mock_socket.sent;
76 : }
77 :
78 140 : void mock_socket_set_response(const uint8_t *data, size_t len) {
79 140 : free(g_mock_socket.response);
80 140 : g_mock_socket.response = (uint8_t *)malloc(len);
81 140 : memcpy(g_mock_socket.response, data, len);
82 140 : g_mock_socket.response_len = len;
83 140 : g_mock_socket.response_pos = 0;
84 140 : }
85 :
86 1396 : void mock_socket_append_response(const uint8_t *data, size_t len) {
87 1396 : if (!data || len == 0) return;
88 1396 : size_t new_len = g_mock_socket.response_len + len;
89 1396 : g_mock_socket.response = (uint8_t *)realloc(g_mock_socket.response, new_len);
90 1396 : memcpy(g_mock_socket.response + g_mock_socket.response_len, data, len);
91 1396 : g_mock_socket.response_len = new_len;
92 : }
93 :
94 29 : void mock_socket_clear_sent(void) {
95 29 : g_mock_socket.sent_len = 0;
96 29 : }
97 :
98 3 : void mock_socket_fail_create(void) { g_mock_socket.fail_create = 1; }
99 4 : void mock_socket_fail_connect(void) { g_mock_socket.fail_connect = 1; }
100 12 : 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 2 : void mock_socket_eintr_send_at(int n) { g_mock_socket.eintr_send_at = n; }
104 2 : void mock_socket_eintr_recv_at(int n) { g_mock_socket.eintr_recv_at = n; }
105 :
106 2 : void mock_socket_set_send_fragment(size_t step) { g_mock_socket.send_fragment = step; }
107 2 : void mock_socket_set_recv_fragment(size_t step) { g_mock_socket.recv_fragment = step; }
108 2 : void mock_socket_inject_eintr_next_send(void) { g_mock_socket.inject_eintr_send = 1; }
109 2 : void mock_socket_inject_eintr_next_recv(void) { g_mock_socket.inject_eintr_recv = 1; }
110 2 : void mock_socket_inject_eagain_next_send(void) { g_mock_socket.inject_eagain_send = 1; }
111 2 : void mock_socket_inject_eagain_next_recv(void) { g_mock_socket.inject_eagain_recv = 1; }
112 2 : void mock_socket_kill_on_next_recv(void) { g_mock_socket.kill_next_recv = 1; }
113 2 : void mock_socket_refuse_connect(void) { g_mock_socket.refuse_connect = 1; }
114 :
115 1900 : void mock_socket_set_on_sent(void (*fn)(const uint8_t *, size_t)) {
116 1900 : g_mock_socket.on_sent = fn;
117 1900 : }
118 :
119 : /* ---- socket.h interface implementation ---- */
120 :
121 573 : int sys_socket_create(void) {
122 573 : if (g_mock_socket.fail_create) {
123 3 : g_mock_socket.fail_create = 0;
124 3 : return -1;
125 : }
126 570 : g_mock_socket.created++;
127 570 : return 42; /* fake fd */
128 : }
129 :
130 568 : int sys_socket_connect(int fd, const char *host, int port) {
131 : (void)fd; (void)host; (void)port;
132 568 : if (g_mock_socket.refuse_connect) {
133 : /* Persistent refusal — every reconnect attempt fails with
134 : * ECONNREFUSED until mock_socket_reset() clears it. */
135 4 : errno = ECONNREFUSED;
136 4 : return -1;
137 : }
138 564 : if (g_mock_socket.fail_connect) {
139 4 : g_mock_socket.fail_connect = 0;
140 4 : return -1;
141 : }
142 560 : g_mock_socket.connected++;
143 560 : return 0;
144 : }
145 :
146 2205 : ssize_t sys_socket_send(int fd, const void *buf, size_t len) {
147 : (void)fd;
148 2205 : if (!buf || len == 0) return 0;
149 :
150 2205 : 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 2205 : if (g_mock_socket.inject_eintr_send) {
155 2 : g_mock_socket.inject_eintr_send = 0;
156 2 : errno = EINTR;
157 2 : return -1;
158 : }
159 2203 : if (g_mock_socket.inject_eagain_send) {
160 2 : g_mock_socket.inject_eagain_send = 0;
161 2 : errno = EAGAIN;
162 2 : return -1;
163 : }
164 :
165 2201 : if (g_mock_socket.eintr_send_at &&
166 3 : g_mock_socket.send_call_n == g_mock_socket.eintr_send_at) {
167 2 : g_mock_socket.eintr_send_at = 0;
168 2 : errno = EINTR;
169 2 : return -1;
170 : }
171 :
172 2199 : if (g_mock_socket.fail_send_at &&
173 15 : g_mock_socket.send_call_n == g_mock_socket.fail_send_at) {
174 12 : g_mock_socket.fail_send_at = 0;
175 12 : 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 2187 : size_t emit = len;
182 2187 : if (g_mock_socket.send_fragment && emit > g_mock_socket.send_fragment) {
183 10 : emit = g_mock_socket.send_fragment;
184 : }
185 :
186 : /* Append to sent buffer */
187 2187 : if (g_mock_socket.sent_len + emit > g_mock_socket.sent_cap) {
188 647 : size_t new_cap = g_mock_socket.sent_cap ? g_mock_socket.sent_cap : 4096;
189 708 : while (new_cap < g_mock_socket.sent_len + emit) new_cap *= 2;
190 647 : g_mock_socket.sent = (uint8_t *)realloc(g_mock_socket.sent, new_cap);
191 647 : g_mock_socket.sent_cap = new_cap;
192 : }
193 2187 : memcpy(g_mock_socket.sent + g_mock_socket.sent_len, buf, emit);
194 2187 : g_mock_socket.sent_len += emit;
195 :
196 2187 : 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 2187 : if (g_mock_socket.on_sent) {
208 1832 : g_mock_socket.on_sent(g_mock_socket.sent, g_mock_socket.sent_len);
209 : }
210 2187 : return (ssize_t)emit;
211 : }
212 :
213 1759 : ssize_t sys_socket_recv(int fd, void *buf, size_t len) {
214 : (void)fd;
215 1759 : if (!buf || len == 0) return 0;
216 :
217 1759 : 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 1759 : if (g_mock_socket.kill_next_recv) {
226 2 : g_mock_socket.kill_next_recv = 0;
227 2 : g_mock_socket.response_pos = g_mock_socket.response_len;
228 2 : return 0;
229 : }
230 :
231 1757 : if (g_mock_socket.inject_eintr_recv) {
232 2 : g_mock_socket.inject_eintr_recv = 0;
233 2 : errno = EINTR;
234 2 : return -1;
235 : }
236 1755 : if (g_mock_socket.inject_eagain_recv) {
237 2 : g_mock_socket.inject_eagain_recv = 0;
238 2 : errno = EAGAIN;
239 2 : return -1;
240 : }
241 :
242 1753 : if (g_mock_socket.eintr_recv_at &&
243 3 : g_mock_socket.recv_call_n == g_mock_socket.eintr_recv_at) {
244 2 : g_mock_socket.eintr_recv_at = 0;
245 2 : errno = EINTR;
246 2 : return -1;
247 : }
248 :
249 1751 : 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 1751 : size_t avail = g_mock_socket.response_len - g_mock_socket.response_pos;
256 1751 : if (avail == 0) return 0; /* EOF */
257 :
258 1738 : size_t to_read = len < avail ? len : avail;
259 1738 : if (g_mock_socket.recv_fragment && to_read > g_mock_socket.recv_fragment) {
260 12 : to_read = g_mock_socket.recv_fragment;
261 : }
262 1738 : memcpy(buf, g_mock_socket.response + g_mock_socket.response_pos, to_read);
263 1738 : g_mock_socket.response_pos += to_read;
264 :
265 1738 : return (ssize_t)to_read;
266 : }
267 :
268 567 : int sys_socket_close(int fd) {
269 : (void)fd;
270 567 : g_mock_socket.closed++;
271 567 : 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 : }
|