The FreeRADIUS server $Id: f3670dba8951ca10eb4948feb3dc3db9423a334f $
Loading...
Searching...
No Matches
bio_base_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 chaining, EOF and shutdown
18 *
19 * @file src/lib/bio/test/bio_base_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/pipe.h>
30
31static int eof_count;
32static int shutdown_count;
33
34static void cb_eof(fr_bio_t *bio) { (void) bio; eof_count++; }
35static int cb_shutdown(fr_bio_t *bio) { (void) bio; shutdown_count++; return 0; }
36static int cb_noop(fr_bio_t *bio) { (void) bio; return 0; }
37
40 .write_resume = cb_noop,
41 .read_blocked = cb_noop,
42 .write_blocked = cb_noop,
43 .eof = cb_eof,
44 .shutdown = cb_shutdown,
45};
46
47/** A pipe in front of a sink, which is the smallest chain we can build without a socket.
48 *
49 * The pipe is the head, and is the bio which can hold data for the application. The sink stands
50 * in for the transport.
51 */
52static fr_bio_t *test_chain_alloc(TALLOC_CTX *ctx, fr_bio_t **sink_p)
53{
54 fr_bio_t *head, *sink;
55
57
58 head = fr_bio_pipe_alloc(ctx, &test_cb, 1024);
59 if (!head) return NULL;
60
61 sink = fr_bio_mem_sink_alloc(ctx, 1024);
62 if (!sink) return NULL;
63
64 fr_bio_chain(head, sink);
65
66 if (sink_p) *sink_p = sink;
67 return head;
68}
69
70static void test_chain(void)
71{
72 TALLOC_CTX *ctx = talloc_init_const("test");
73 fr_bio_t *head, *sink;
74
75 head = test_chain_alloc(ctx, &sink);
76 TEST_CHECK(head != NULL);
77 if (!head) goto done;
78
79 TEST_CASE("the head of the chain is the first bio");
80 TEST_CHECK(fr_bio_head(sink) == head);
82
83 TEST_CASE("next and prev are each other's inverse");
84 TEST_CHECK(fr_bio_next(head) == sink);
85 TEST_CHECK(fr_bio_prev(sink) == head);
86 TEST_CHECK(fr_bio_next(sink) == NULL);
87 TEST_CHECK(fr_bio_prev(head) == NULL);
88
89done:
90 talloc_free(ctx);
91}
92
93/** With nothing buffered, a shutdown tears the chain down at once.
94 */
95static void test_shutdown_immediate(void)
96{
97 TALLOC_CTX *ctx = talloc_init_const("test");
98 fr_bio_t *head, *sink;
99 uint8_t buf[16];
100
101 head = test_chain_alloc(ctx, &sink);
102 TEST_CHECK(head != NULL);
103 if (!head) goto done;
104
105 TEST_CASE("shutdown of an empty chain completes");
106 TEST_CHECK(fr_bio_shutdown(sink) == 0);
108
109 TEST_CASE("reads and writes report SHUTDOWN afterwards");
110 TEST_CHECK(fr_bio_read(head, NULL, buf, sizeof(buf)) == fr_bio_error(SHUTDOWN));
111 TEST_CHECK(fr_bio_write(head, NULL, "x", 1) == fr_bio_error(SHUTDOWN));
112
113 TEST_CASE("a second shutdown is a no-op");
114 TEST_CHECK(fr_bio_shutdown(sink) == 0);
116
117done:
118 talloc_free(ctx);
119}
120
121/** A bio which still holds data defers the teardown until the application has drained it.
122 */
124{
125 TALLOC_CTX *ctx = talloc_init_const("test");
126 fr_bio_t *head, *sink;
127 uint8_t buf[32];
128 ssize_t slen;
129
130 head = test_chain_alloc(ctx, &sink);
131 TEST_CHECK(head != NULL);
132 if (!head) goto done;
133
134 TEST_CHECK(fr_bio_write(head, NULL, "hello world", 11) == 11);
135
136 TEST_CASE("the teardown waits while a bio still holds data");
137 TEST_CHECK(fr_bio_shutdown(sink) == 0);
140
141 TEST_CASE("writes stop at once even though reads do not");
142 TEST_CHECK(fr_bio_write(head, NULL, "x", 1) < 0);
143
144 TEST_CASE("the application still gets the data which had arrived");
145 slen = fr_bio_read(head, NULL, buf, sizeof(buf));
146 TEST_CHECK_RET((int) slen, 11);
147 if (slen == 11) TEST_CHECK(memcmp(buf, "hello world", 11) == 0);
148
149 TEST_CASE("draining the last byte completes the shutdown");
152
153 TEST_CASE("the chain reports SHUTDOWN once it is torn down");
154 TEST_CHECK(fr_bio_read(head, NULL, buf, sizeof(buf)) == fr_bio_error(SHUTDOWN));
155
156done:
157 talloc_free(ctx);
158}
159
160/** fr_bio_shutdown_discard() does not wait, and throws the buffered data away.
161 */
162static void test_shutdown_discard(void)
163{
164 TALLOC_CTX *ctx = talloc_init_const("test");
165 fr_bio_t *head, *sink;
166 uint8_t buf[32];
167
168 head = test_chain_alloc(ctx, &sink);
169 TEST_CHECK(head != NULL);
170 if (!head) goto done;
171
172 TEST_CHECK(fr_bio_write(head, NULL, "hello world", 11) == 11);
173
174 TEST_CASE("discard tears the chain down immediately");
177
178 TEST_CASE("the buffered data is not handed over");
179 TEST_CHECK(fr_bio_read(head, NULL, buf, sizeof(buf)) == fr_bio_error(SHUTDOWN));
180
181done:
182 talloc_free(ctx);
183}
184
185/** A free completes a shutdown which is still waiting for a drain.
186 */
188{
189 TALLOC_CTX *ctx = talloc_init_const("test");
190 fr_bio_t *head, *sink;
191
192 head = test_chain_alloc(ctx, &sink);
193 TEST_CHECK(head != NULL);
194 if (!head) goto done;
195
196 TEST_CHECK(fr_bio_write(head, NULL, "hello world", 11) == 11);
197 TEST_CHECK(fr_bio_shutdown(sink) == 0);
198
199 TEST_CASE("the teardown has not run yet");
201
202 TEST_CASE("freeing the chain finishes it");
203 TALLOC_FREE(head);
205
206done:
207 talloc_free(ctx);
208}
209
210static void test_strerror(void)
211{
212 TEST_CASE("every error code has a message");
213 TEST_CHECK(strcmp(fr_bio_strerror(fr_bio_error(NONE)), "<unknown>") != 0);
214 TEST_CHECK(strcmp(fr_bio_strerror(fr_bio_error(GENERIC)), "<unknown>") != 0);
215 TEST_CHECK(strcmp(fr_bio_strerror(fr_bio_error(IO_WOULD_BLOCK)), "<unknown>") != 0);
216 TEST_CHECK(strcmp(fr_bio_strerror(fr_bio_error(IO)), "<unknown>") != 0);
217 TEST_CHECK(strcmp(fr_bio_strerror(fr_bio_error(VERIFY)), "<unknown>") != 0);
218 TEST_CHECK(strcmp(fr_bio_strerror(fr_bio_error(BUFFER_FULL)), "<unknown>") != 0);
219 TEST_CHECK(strcmp(fr_bio_strerror(fr_bio_error(BUFFER_TOO_SMALL)), "<unknown>") != 0);
220 TEST_CHECK(strcmp(fr_bio_strerror(fr_bio_error(SHUTDOWN)), "<unknown>") != 0);
221
222 TEST_CASE("OOM has a message too");
223 TEST_MSG("summary.md records this as missing");
224 TEST_CHECK(strcmp(fr_bio_strerror(fr_bio_error(OOM)), "<unknown>") != 0);
225}
226
228 { "chain", test_chain },
229 { "shutdown_immediate", test_shutdown_immediate },
230 { "shutdown_drains_reads", test_shutdown_drains_reads },
231 { "shutdown_discard", test_shutdown_discard },
232 { "shutdown_free_completes", test_shutdown_free_completes },
233 { "strerror", test_strerror },
235};
#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_MSG(...)
Definition acutest.h:234
#define TEST_CHECK_RET(_got, _exp)
static fr_bio_t * fr_bio_prev(fr_bio_t *bio)
Definition base.h:122
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
static fr_bio_t * fr_bio_next(fr_bio_t *bio)
Definition base.h:131
static fr_bio_t * fr_bio_head(fr_bio_t *bio)
Definition base.h:140
static ssize_t fr_bio_read(fr_bio_t *bio, void *packet_ctx, void *buffer, size_t size)
Read raw data from a bio.
Definition base.h:161
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
TEST_LIST
static void test_strerror(void)
static void cb_eof(fr_bio_t *bio)
static void test_chain(void)
static fr_bio_t * test_chain_alloc(TALLOC_CTX *ctx, fr_bio_t **sink_p)
A pipe in front of a sink, which is the smallest chain we can build without a socket.
static fr_bio_cb_funcs_t test_cb
static void test_shutdown_drains_reads(void)
A bio which still holds data defers the teardown until the application has drained it.
static void test_shutdown_immediate(void)
With nothing buffered, a shutdown tears the chain down at once.
static void test_shutdown_discard(void)
fr_bio_shutdown_discard() does not wait, and throws the buffered data away.
static int eof_count
static int cb_shutdown(fr_bio_t *bio)
static int shutdown_count
static void test_shutdown_free_completes(void)
A free completes a shutdown which is still waiting for a drain.
static int cb_noop(fr_bio_t *bio)
static void fr_bio_chain(fr_bio_t *first, fr_bio_t *second)
Chain one bio after another.
Definition bio_priv.h:86
talloc_free(hp)
char const * fr_bio_strerror(ssize_t error)
Definition base.c:225
int fr_bio_shutdown_discard(fr_bio_t *bio)
Shut down the chain now, discarding anything which is still buffered.
Definition base.c:207
int fr_bio_shutdown(fr_bio_t *bio)
Shut down a set of BIOs.
Definition base.c:158
fr_bio_t * fr_bio_mem_sink_alloc(TALLOC_CTX *ctx, size_t read_size)
Allocate a memory buffer which sinks data from a bio system into the callers application.
Definition mem.c:820
long int ssize_t
unsigned char uint8_t
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
#define VERIFY(_x)
Definition trie.c:130
static fr_slen_t head
Definition xlat.h:410