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