The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
Loading...
Searching...
No Matches
bio_pipe_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 pipe bio
18 *
19 * @file src/lib/bio/test/bio_pipe_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;
32
33static void cb_eof(fr_bio_t *bio) { (void) bio; eof_count++; }
34static int cb_read_blocked(fr_bio_t *bio) { (void) bio; read_blocked_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_read_blocked,
41 .write_blocked = cb_noop,
42 .eof = cb_eof,
43};
44
45static fr_bio_t *test_pipe_alloc(TALLOC_CTX *ctx, size_t size)
46{
48
49 return fr_bio_pipe_alloc(ctx, &test_cb, size);
50}
51
52static void test_write_read(void)
53{
54 TALLOC_CTX *ctx = talloc_init_const("test");
55 fr_bio_t *bio;
56 uint8_t buf[32];
57 ssize_t slen;
58
59 bio = test_pipe_alloc(ctx, 1024);
60 TEST_CHECK(bio != NULL);
61 if (!bio) goto done;
62
63 TEST_CASE("a write is accepted whole");
64 TEST_CHECK_RET((int) fr_bio_write(bio, NULL, "hello", 5), 5);
65
66 TEST_CASE("a read returns what was written");
67 slen = fr_bio_read(bio, NULL, buf, sizeof(buf));
68 TEST_CHECK_RET((int) slen, 5);
69 if (slen == 5) TEST_CHECK(memcmp(buf, "hello", 5) == 0);
70
71 TEST_CASE("an empty pipe reads nothing, and says so");
72 TEST_CHECK_RET((int) fr_bio_read(bio, NULL, buf, sizeof(buf)), 0);
75
76done:
77 talloc_free(ctx);
78}
79
80/** A write larger than the buffer is clamped rather than refused.
81 */
82static void test_write_clamped(void)
83{
84 TALLOC_CTX *ctx = talloc_init_const("test");
85 fr_bio_t *bio;
86 uint8_t data[2048];
87
88 bio = test_pipe_alloc(ctx, 1024);
89 TEST_CHECK(bio != NULL);
90 if (!bio) goto done;
91
92 memset(data, 'a', sizeof(data));
93
94 TEST_CASE("the write is clamped to the room available");
95 TEST_CHECK_RET((int) fr_bio_write(bio, NULL, data, sizeof(data)), 1024);
96
97 TEST_CASE("a full pipe accepts nothing more");
98 TEST_CHECK_RET((int) fr_bio_write(bio, NULL, data, 1), 0);
99
100done:
101 talloc_free(ctx);
102}
103
104/** EOF is signalled even when the read which notices it returns no data.
105 *
106 * The pipe used to call read_blocked() in that case, telling an application which read until it
107 * got nothing that it was blocked rather than finished.
108 */
109static void test_eof_on_empty_read(void)
110{
111 TALLOC_CTX *ctx = talloc_init_const("test");
112 fr_bio_t *bio, *sink;
113 uint8_t buf[32];
114
115 bio = test_pipe_alloc(ctx, 1024);
116 TEST_CHECK(bio != NULL);
117 if (!bio) goto done;
118
119 sink = fr_bio_mem_sink_alloc(ctx, 1024);
120 TEST_CHECK(sink != NULL);
121 if (!sink) goto done;
122
123 fr_bio_chain(bio, sink);
124
125 TEST_CHECK_RET((int) fr_bio_write(bio, NULL, "hi", 2), 2);
126
127 /*
128 * The sink is at EOF. The pipe still holds two bytes, so the walk stops there.
129 */
130 TEST_CHECK(fr_bio_eof(sink) == bio);
132
133 TEST_CASE("the buffered data is still delivered");
134 TEST_CHECK_RET((int) fr_bio_read(bio, NULL, buf, sizeof(buf)), 2);
135
136 TEST_CASE("EOF is signalled once the buffer empties");
139
140done:
141 talloc_free(ctx);
142}
143
144/** An empty pipe must not stop the EOF walk.
145 *
146 * fr_bio_eof() stops when a handler returns zero or less. The pipe used to return zero for
147 * "nothing buffered", which stopped the walk and left a chain containing a pipe unable to finish.
148 */
149static void test_eof_walk_continues(void)
150{
151 TALLOC_CTX *ctx = talloc_init_const("test");
152 fr_bio_t *bio, *sink;
153
154 bio = test_pipe_alloc(ctx, 1024);
155 TEST_CHECK(bio != NULL);
156 if (!bio) goto done;
157
158 sink = fr_bio_mem_sink_alloc(ctx, 1024);
159 TEST_CHECK(sink != NULL);
160 if (!sink) goto done;
161
162 fr_bio_chain(bio, sink);
163
164 TEST_CASE("an empty pipe lets the walk reach the head");
165 TEST_CHECK(fr_bio_eof(sink) == NULL);
167
168done:
169 talloc_free(ctx);
170}
171
172/** A write after EOF reports an error rather than "try again later".
173 */
174static void test_write_after_eof(void)
175{
176 TALLOC_CTX *ctx = talloc_init_const("test");
177 fr_bio_t *bio, *sink;
178
179 bio = test_pipe_alloc(ctx, 1024);
180 TEST_CHECK(bio != NULL);
181 if (!bio) goto done;
182
183 sink = fr_bio_mem_sink_alloc(ctx, 1024);
184 TEST_CHECK(sink != NULL);
185 if (!sink) goto done;
186
187 fr_bio_chain(bio, sink);
188
189 TEST_CHECK(fr_bio_eof(sink) == NULL);
190
191 TEST_CASE("a write after EOF is an error, not a zero");
192 TEST_CHECK(fr_bio_write(bio, NULL, "x", 1) < 0);
193
194done:
195 talloc_free(ctx);
196}
197
199 { "write_read", test_write_read },
200 { "write_clamped", test_write_clamped },
201 { "eof_on_empty_read", test_eof_on_empty_read },
202 { "eof_walk_continues", test_eof_walk_continues },
203 { "write_after_eof", test_write_after_eof },
205};
#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)
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 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
static void test_write_read(void)
TEST_LIST
static void cb_eof(fr_bio_t *bio)
static fr_bio_cb_funcs_t test_cb
static int cb_read_blocked(fr_bio_t *bio)
static void test_eof_walk_continues(void)
An empty pipe must not stop the EOF walk.
static fr_bio_t * test_pipe_alloc(TALLOC_CTX *ctx, size_t size)
static int eof_count
static void test_eof_on_empty_read(void)
EOF is signalled even when the read which notices it returns no data.
static int read_blocked_count
static void test_write_after_eof(void)
A write after EOF reports an error rather than "try again later".
static void test_write_clamped(void)
A write larger than the buffer is clamped rather than refused.
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)
fr_bio_t * fr_bio_eof(fr_bio_t *bio)
Internal BIO function to run EOF callbacks.
Definition base.c:279
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
static fr_slen_t data
Definition value.h:1366