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