Line data Source code
1 : /* SPDX-License-Identifier: GPL-3.0-or-later */
2 : /* Copyright 2026 Peter Csaszar */
3 :
4 : /**
5 : * @file test_transport.c
6 : * @brief Unit tests for transport EINTR retry behaviour.
7 : *
8 : * Verifies that transport_send() and transport_recv() transparently retry
9 : * when the underlying sys_socket_send / sys_socket_recv call is interrupted
10 : * by a signal (errno == EINTR).
11 : */
12 :
13 : #include "test_helpers.h"
14 : #include "transport.h"
15 : #include "mock_socket.h"
16 :
17 : #include <stdint.h>
18 : #include <string.h>
19 :
20 : /* ------------------------------------------------------------------ *
21 : * Helpers *
22 : * ------------------------------------------------------------------ */
23 :
24 : /**
25 : * Build an abridged-framed payload in `out` suitable for
26 : * mock_socket_set_response(). payload must be 4-byte aligned.
27 : * Returns total frame length written.
28 : */
29 2 : static size_t make_abridged_frame(uint8_t *out, const uint8_t *payload,
30 : size_t payload_len)
31 : {
32 2 : size_t wire_len = payload_len / 4;
33 2 : size_t off = 0;
34 2 : if (wire_len < 0x7F) {
35 2 : out[off++] = (uint8_t)wire_len;
36 : } else {
37 0 : out[off++] = 0x7F;
38 0 : out[off++] = (uint8_t)(wire_len & 0xFF);
39 0 : out[off++] = (uint8_t)((wire_len >> 8) & 0xFF);
40 0 : out[off++] = (uint8_t)((wire_len >> 16) & 0xFF);
41 : }
42 2 : memcpy(out + off, payload, payload_len);
43 2 : return off + payload_len;
44 : }
45 :
46 : /* ------------------------------------------------------------------ *
47 : * Tests *
48 : * ------------------------------------------------------------------ */
49 :
50 : /**
51 : * transport_send() should succeed even when the first sys_socket_send call
52 : * for the payload returns -1/EINTR.
53 : *
54 : * Call sequence inside transport_send (after connect which uses send #1 for
55 : * the 0xEF abridged marker):
56 : * call 1 (abridged marker, during connect): succeeds
57 : * call 2 (1-byte length prefix): succeeds
58 : * call 3 (payload): EINTR on call 3 → retry → succeeds
59 : */
60 1 : static void test_transport_send_eintr_retry(void) {
61 1 : mock_socket_reset();
62 :
63 : Transport t;
64 1 : transport_init(&t);
65 1 : int rc = transport_connect(&t, "127.0.0.1", 443);
66 1 : ASSERT(rc == 0, "connect should succeed");
67 :
68 : /* Prime EINTR on the 3rd sys_socket_send call (payload chunk) */
69 1 : mock_socket_eintr_send_at(3);
70 :
71 1 : uint8_t payload[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
72 1 : rc = transport_send(&t, payload, sizeof(payload));
73 1 : ASSERT(rc == 0, "transport_send should succeed after EINTR retry");
74 :
75 1 : transport_close(&t);
76 : }
77 :
78 : /**
79 : * transport_send() with EINTR on the length-prefix send (call 2) also retries.
80 : */
81 1 : static void test_transport_send_eintr_on_prefix(void) {
82 1 : mock_socket_reset();
83 :
84 : Transport t;
85 1 : transport_init(&t);
86 1 : int rc = transport_connect(&t, "127.0.0.1", 443);
87 1 : ASSERT(rc == 0, "connect should succeed");
88 :
89 : /* EINTR on 2nd call (length prefix) */
90 1 : mock_socket_eintr_send_at(2);
91 :
92 1 : uint8_t payload[4] = {0xAA, 0xBB, 0xCC, 0xDD};
93 1 : rc = transport_send(&t, payload, sizeof(payload));
94 1 : ASSERT(rc == 0, "transport_send should succeed after EINTR on prefix");
95 :
96 1 : transport_close(&t);
97 : }
98 :
99 : /**
100 : * transport_recv() should succeed even when the first recv for the length
101 : * prefix byte returns -1/EINTR.
102 : *
103 : * recv call sequence:
104 : * call 1: length prefix byte — inject EINTR → retry → succeeds
105 : * call 2: payload bytes — succeeds
106 : */
107 1 : static void test_transport_recv_eintr_on_length(void) {
108 1 : mock_socket_reset();
109 :
110 : Transport t;
111 1 : transport_init(&t);
112 1 : transport_connect(&t, "127.0.0.1", 443);
113 :
114 : /* Build a canned response frame */
115 1 : uint8_t payload[4] = {0x11, 0x22, 0x33, 0x44};
116 : uint8_t frame[16];
117 1 : size_t frame_len = make_abridged_frame(frame, payload, sizeof(payload));
118 1 : mock_socket_set_response(frame, frame_len);
119 :
120 : /* EINTR on 1st recv (length prefix byte) */
121 1 : mock_socket_eintr_recv_at(1);
122 :
123 : uint8_t buf[64];
124 1 : size_t out_len = 0;
125 1 : int rc = transport_recv(&t, buf, sizeof(buf), &out_len);
126 1 : ASSERT(rc == 0, "transport_recv should succeed after EINTR on length prefix");
127 1 : ASSERT(out_len == 4, "should have received 4 payload bytes");
128 1 : ASSERT(memcmp(buf, payload, 4) == 0, "payload bytes should match");
129 :
130 1 : transport_close(&t);
131 : }
132 :
133 : /**
134 : * transport_recv() should succeed when EINTR fires during the payload read.
135 : *
136 : * recv call sequence:
137 : * call 1: length prefix byte — succeeds
138 : * call 2: payload chunk — inject EINTR → retry → succeeds
139 : */
140 1 : static void test_transport_recv_eintr_on_payload(void) {
141 1 : mock_socket_reset();
142 :
143 : Transport t;
144 1 : transport_init(&t);
145 1 : transport_connect(&t, "127.0.0.1", 443);
146 :
147 1 : uint8_t payload[8] = {0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE};
148 : uint8_t frame[32];
149 1 : size_t frame_len = make_abridged_frame(frame, payload, sizeof(payload));
150 1 : mock_socket_set_response(frame, frame_len);
151 :
152 : /* EINTR on 2nd recv (payload chunk) */
153 1 : mock_socket_eintr_recv_at(2);
154 :
155 : uint8_t buf[64];
156 1 : size_t out_len = 0;
157 1 : int rc = transport_recv(&t, buf, sizeof(buf), &out_len);
158 1 : ASSERT(rc == 0, "transport_recv should succeed after EINTR on payload");
159 1 : ASSERT(out_len == 8, "should have received 8 payload bytes");
160 1 : ASSERT(memcmp(buf, payload, 8) == 0, "payload bytes should match");
161 :
162 1 : transport_close(&t);
163 : }
164 :
165 : /* ------------------------------------------------------------------ *
166 : * Suite entry point *
167 : * ------------------------------------------------------------------ */
168 :
169 1 : void run_transport_eintr_tests(void) {
170 1 : RUN_TEST(test_transport_send_eintr_retry);
171 1 : RUN_TEST(test_transport_send_eintr_on_prefix);
172 1 : RUN_TEST(test_transport_recv_eintr_on_length);
173 1 : RUN_TEST(test_transport_recv_eintr_on_payload);
174 1 : }
|