LCOV - code coverage report
Current view: top level - tests/mocks - socket.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 90.5 % 137 124
Test Date: 2026-04-20 19:54:22 Functions: 90.0 % 30 27

            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              : }
        

Generated by: LCOV version 2.0-1