The FreeRADIUS server $Id: f3670dba8951ca10eb4948feb3dc3db9423a334f $
Loading...
Searching...
No Matches
bio_queue_tests.c
Go to the documentation of this file.
1/*
2 * This library is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU Lesser General Public
4 * License as published by the Free Software Foundation; either
5 * version 2.1 of the License, or (at your option) any later version.
6 *
7 * This library is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * Lesser General Public License for more details.
11 *
12 * You should have received a copy of the GNU Lesser General Public
13 * License along with this library; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/** Tests for the queue bio's shutdown and error handling
18 *
19 * @file src/lib/bio/test/bio_queue_tests.c
20 *
21 * @copyright 2026 Network RADIUS SAS (legal@networkradius.com)
22 */
23#include <freeradius-devel/util/test/acutest_common_init.h>
24#include <freeradius-devel/util/test/acutest_helpers.h>
25
26#define _BIO_PRIVATE 1
27#include <freeradius-devel/bio/bio_priv.h>
28#include <freeradius-devel/bio/mem.h>
29#include <freeradius-devel/bio/null.h>
30#include <freeradius-devel/bio/pipe.h>
31#include <freeradius-devel/bio/queue.h>
32
33static int cancel_count;
34static int shutdown_count;
35
36static void cb_eof(fr_bio_t *bio) { (void) bio; }
37static int cb_shutdown(fr_bio_t *bio) { (void) bio; shutdown_count++; return 0; }
38static int cb_noop(fr_bio_t *bio) { (void) bio; return 0; }
39
40static void queue_cancel(fr_bio_t *bio, void *packet_ctx, void const *buffer, size_t size)
41{
42 (void) bio; (void) packet_ctx; (void) buffer; (void) size;
44}
45
48 .write_resume = cb_noop,
49 .read_blocked = cb_noop,
50 .write_blocked = cb_noop,
51 .eof = cb_eof,
52 .shutdown = cb_shutdown,
53};
54
55/*
56 * The pipe holds 1024 bytes, so the second write is partial, and the queue saves it.
57 */
58static uint8_t fill[1000];
59static uint8_t overflow[100];
60
61/** A stub transport which fails recoverably or fatally, as each test chooses.
62 *
63 * On a recoverable failure, the stub returns an error and changes nothing else. On a fatal failure,
64 * the stub first shuts the chain down, as the fd bio does. Only the failing bio decides whether a
65 * failure is recoverable or fatal.
66 */
67typedef struct {
69 bool fatal; //!< shut the chain down before failing
70} stub_t;
71
72static ssize_t stub_write_fail(fr_bio_t *bio, UNUSED void *packet_ctx, UNUSED void const *buffer, UNUSED size_t size)
73{
74 stub_t *stub = (stub_t *) bio;
75
76 if (stub->fatal) (void) fr_bio_shutdown(bio);
77
78 fr_strerror_const("stub write failure");
79 return fr_bio_error(GENERIC);
80}
81
82/** A transport which accepts one byte of each write, so the queue bio saves the rest of the packet.
83 */
84static ssize_t stub_write_one(UNUSED fr_bio_t *bio, UNUSED void *packet_ctx, UNUSED void const *buffer, UNUSED size_t size)
85{
86 return 1;
87}
88
89/** A transport which accepts every write in full.
90 */
91static ssize_t stub_write_all(UNUSED fr_bio_t *bio, UNUSED void *packet_ctx, UNUSED void const *buffer, size_t size)
92{
93 return size;
94}
95
96static fr_bio_t *stub_alloc(TALLOC_CTX *ctx)
97{
98 stub_t *stub;
99
100 stub = talloc_zero(ctx, stub_t);
101 if (!stub) return NULL;
102
103 stub->bio.read = fr_bio_null_read;
104 stub->bio.write = stub_write_fail;
105
106 return &stub->bio;
107}
108
109/** Leave one packet partly written in the queue.
110 */
112{
113 if (fr_bio_write(head, NULL, fill, sizeof(fill)) != sizeof(fill)) return false;
114
115 return (fr_bio_write(head, NULL, overflow, sizeof(overflow)) == sizeof(overflow));
116}
117
118/** The application installs its callbacks on a queue bio at the head of the chain.
119 *
120 * The queue's own shutdown routine must survive that, and still cancel the saved packet.
121 */
123{
124 TALLOC_CTX *ctx = talloc_init_const("test");
125 fr_bio_t *queue, *pipe;
126
128
129 pipe = fr_bio_pipe_alloc(ctx, &test_cb, 1024);
130 TEST_CHECK(pipe != NULL);
131 if (!pipe) goto done;
132
133 queue = fr_bio_queue_alloc(ctx, 4, NULL, NULL, queue_cancel, pipe);
134 TEST_CHECK(queue != NULL);
135 if (!queue) goto done;
136
137 fr_bio_cb_set(queue, &test_cb);
138
139 TEST_CHECK(queue_fill(queue));
140
141 TEST_CASE("the transport dies");
142 TEST_CHECK(fr_bio_shutdown(pipe) == 0);
143
144 TEST_CASE("the saved packet is cancelled");
146
147 TEST_CASE("the application is told about the shutdown");
149
150 TEST_CASE("writes report SHUTDOWN");
151 TEST_CHECK(fr_bio_write(queue, NULL, "x", 1) == fr_bio_error(SHUTDOWN));
152
153done:
154 talloc_free(ctx);
155}
156
157/** A queue bio in the middle of a chain is not the head, so only the private slot reaches it.
158 */
159static void test_shutdown_not_head(void)
160{
161 TALLOC_CTX *ctx = talloc_init_const("test");
162 fr_bio_t *head, *queue, *pipe;
163
165
166 pipe = fr_bio_pipe_alloc(ctx, &test_cb, 1024);
167 TEST_CHECK(pipe != NULL);
168 if (!pipe) goto done;
169
170 queue = fr_bio_queue_alloc(ctx, 4, NULL, NULL, queue_cancel, pipe);
171 TEST_CHECK(queue != NULL);
172 if (!queue) goto done;
173
174 /*
175 * A memory bio with no buffers passes writes straight through.
176 */
177 head = fr_bio_mem_alloc(ctx, 0, 0, queue);
178 TEST_CHECK(head != NULL);
179 if (!head) goto done;
180
182
184
185 TEST_CASE("the transport dies");
186 TEST_CHECK(fr_bio_shutdown(pipe) == 0);
187
188 TEST_CASE("the saved packet is cancelled");
190
191 TEST_CASE("the application is told about the shutdown");
193
194done:
195 talloc_free(ctx);
196}
197
198/** Build a queue in front of a stub, with one packet saved after a partial write.
199 */
200static fr_bio_t *test_queue_saved_alloc(TALLOC_CTX *ctx, fr_bio_t **stub_p)
201{
202 fr_bio_t *queue, *stub;
203
205
206 stub = stub_alloc(ctx);
207 if (!stub) return NULL;
208
209 queue = fr_bio_queue_alloc(ctx, 4, NULL, NULL, queue_cancel, stub);
210 if (!queue) return NULL;
211
212 fr_bio_cb_set(queue, &test_cb);
213
214 stub->write = stub_write_one;
215 if (fr_bio_write(queue, NULL, overflow, sizeof(overflow)) != sizeof(overflow)) return NULL;
216
217 *stub_p = stub;
218 return queue;
219}
220
221/** The queue bio passes a recoverable write error up to the application, and keeps working.
222 */
224{
225 TALLOC_CTX *ctx = talloc_init_const("test");
226 fr_bio_t *queue, *stub;
227
229
230 stub = stub_alloc(ctx);
231 TEST_CHECK(stub != NULL);
232 if (!stub) goto done;
233
234 queue = fr_bio_queue_alloc(ctx, 4, NULL, NULL, queue_cancel, stub);
235 TEST_CHECK(queue != NULL);
236 if (!queue) goto done;
237
238 fr_bio_cb_set(queue, &test_cb);
239
240 TEST_CASE("the write gets the error from the stub");
241 TEST_CHECK(fr_bio_write(queue, NULL, "x", 1) == fr_bio_error(GENERIC));
242
243 TEST_CASE("the queue bio does not shut the chain down");
245
246 TEST_CASE("the next write reaches the stub again");
247 stub->write = stub_write_all;
248 TEST_CHECK_RET((int) fr_bio_write(queue, NULL, "x", 1), 1);
249
250done:
251 talloc_free(ctx);
252}
253
254/** The queue bio passes a recoverable flush error up to the application, and keeps the saved packet.
255 */
257{
258 TALLOC_CTX *ctx = talloc_init_const("test");
259 fr_bio_t *queue, *stub = NULL;
260
261 queue = test_queue_saved_alloc(ctx, &stub);
262 TEST_CHECK(queue != NULL);
263 if (!queue) goto done;
264
265 TEST_CASE("the flush gets the error from the stub");
266 stub->write = stub_write_fail;
267 TEST_CHECK(fr_bio_write(queue, NULL, NULL, SIZE_MAX) == fr_bio_error(GENERIC));
268
269 TEST_CASE("the saved packet is not cancelled, and the chain is not shut down");
272
273 TEST_CASE("a later flush sends the saved packet");
274 stub->write = stub_write_all;
275 TEST_CHECK(fr_bio_write(queue, NULL, NULL, SIZE_MAX) > 0);
277
278done:
279 talloc_free(ctx);
280}
281
282/** When the next bio shuts the chain down on a fatal flush error, fr_bio_queue_shutdown() cancels the saved packet.
283 */
285{
286 TALLOC_CTX *ctx = talloc_init_const("test");
287 fr_bio_t *queue, *stub = NULL;
288
289 queue = test_queue_saved_alloc(ctx, &stub);
290 TEST_CHECK(queue != NULL);
291 if (!queue) goto done;
292
293 TEST_CASE("the flush gets the error from the stub");
294 ((stub_t *) stub)->fatal = true;
295 stub->write = stub_write_fail;
296 TEST_CHECK(fr_bio_write(queue, NULL, NULL, SIZE_MAX) == fr_bio_error(GENERIC));
297
298 TEST_CASE("fr_bio_queue_shutdown() cancels the saved packet, and the application is told");
301
302 TEST_CASE("later writes report SHUTDOWN");
303 TEST_CHECK(fr_bio_write(queue, NULL, "x", 1) == fr_bio_error(SHUTDOWN));
304
305done:
306 talloc_free(ctx);
307}
308
310 { "shutdown_keeps_app_callback", test_shutdown_keeps_app_callback },
311 { "shutdown_not_head", test_shutdown_not_head },
312 { "recoverable_write_passes_up", test_recoverable_write_passes_up },
313 { "recoverable_flush_passes_up", test_recoverable_flush_passes_up },
314 { "fatal_flush_from_next", test_fatal_flush_from_next },
316};
static int const char char buffer[256]
Definition acutest.h:635
#define TEST_CHECK(cond)
Definition acutest.h:101
#define TEST_CASE(name)
Definition acutest.h:203
#define TEST_TERMINATOR
Definition acutest.h:77
#define TEST_CHECK_RET(_got, _exp)
fr_bio_write_t _CONST write
write to the underlying bio
Definition base.h:117
static ssize_t fr_bio_write(fr_bio_t *bio, void *packet_ctx, void const *buffer, size_t size)
Write raw data to a bio.
Definition base.h:184
fr_bio_io_t read_resume
"unblocked" is too similar to "blocked"
Definition base.h:97
#define fr_bio_error(_x)
Definition base.h:200
fr_bio_t * fr_bio_queue_alloc(TALLOC_CTX *ctx, size_t max_saved, fr_bio_queue_saved_t saved, fr_bio_queue_callback_t sent, fr_bio_queue_callback_t cancel, fr_bio_t *next)
Allocate a packet-based bio.
Definition queue.c:351
static int packet_ctx
the tests pass only the address of packet_ctx
#define FR_BIO_COMMON
Common elements at the start of each private fr_bio_t.
Definition bio_priv.h:50
static void test_shutdown_keeps_app_callback(void)
The application installs its callbacks on a queue bio at the head of the chain.
TEST_LIST
static void test_recoverable_flush_passes_up(void)
The queue bio passes a recoverable flush error up to the application, and keeps the saved packet.
static void cb_eof(fr_bio_t *bio)
static fr_bio_cb_funcs_t test_cb
static ssize_t stub_write_one(UNUSED fr_bio_t *bio, UNUSED void *packet_ctx, UNUSED void const *buffer, UNUSED size_t size)
A transport which accepts one byte of each write, so the queue bio saves the rest of the packet.
static ssize_t stub_write_all(UNUSED fr_bio_t *bio, UNUSED void *packet_ctx, UNUSED void const *buffer, size_t size)
A transport which accepts every write in full.
static uint8_t fill[1000]
static void test_recoverable_write_passes_up(void)
The queue bio passes a recoverable write error up to the application, and keeps working.
bool fatal
shut the chain down before failing
static void queue_cancel(fr_bio_t *bio, void *packet_ctx, void const *buffer, size_t size)
static bool queue_fill(fr_bio_t *head)
Leave one packet partly written in the queue.
static int cb_shutdown(fr_bio_t *bio)
static void test_fatal_flush_from_next(void)
When the next bio shuts the chain down on a fatal flush error, fr_bio_queue_shutdown() cancels the sa...
static fr_bio_t * test_queue_saved_alloc(TALLOC_CTX *ctx, fr_bio_t **stub_p)
Build a queue in front of a stub, with one packet saved after a partial write.
static void test_shutdown_not_head(void)
A queue bio in the middle of a chain is not the head, so only the private slot reaches it.
static fr_bio_t * stub_alloc(TALLOC_CTX *ctx)
static ssize_t stub_write_fail(fr_bio_t *bio, UNUSED void *packet_ctx, UNUSED void const *buffer, UNUSED size_t size)
static int cancel_count
static int shutdown_count
static uint8_t overflow[100]
static int cb_noop(fr_bio_t *bio)
A stub transport which fails recoverably or fatally, as each test chooses.
#define UNUSED
Definition build.h:384
talloc_free(hp)
void fr_bio_cb_set(fr_bio_t *bio, fr_bio_cb_funcs_t const *cb)
Definition base.c:260
int fr_bio_shutdown(fr_bio_t *bio)
Shut down a set of BIOs.
Definition base.c:158
fr_bio_t * fr_bio_mem_alloc(TALLOC_CTX *ctx, size_t read_size, size_t write_size, fr_bio_t *next)
Allocate a memory buffer bio.
Definition mem.c:679
long int ssize_t
unsigned char uint8_t
ssize_t fr_bio_null_read(UNUSED fr_bio_t *bio, UNUSED void *packet_ctx, UNUSED void *buffer, UNUSED size_t size)
Always return 0 on read.
Definition null.c:31
fr_bio_t * fr_bio_pipe_alloc(TALLOC_CTX *ctx, fr_bio_cb_funcs_t *cb, size_t buffer_size)
Allocate a thread-safe pipe which can be used for both reads and writes.
Definition pipe.c:187
static bool done
Definition radclient.c:80
static TALLOC_CTX * talloc_init_const(char const *name)
Allocate a top level chunk with a constant name.
Definition talloc.h:127
static fr_slen_t head
Definition xlat.h:410
#define fr_strerror_const(_msg)
Definition strerror.h:223