7dc6a9cf35f40b7d41024a0ec8d1c649672db20b
[openssl.git] / test / helpers / noisydgrambio.c
1 /*
2  * Copyright 2023 The OpenSSL Project Authors. All Rights Reserved.
3  *
4  * Licensed under the Apache License 2.0 (the "License").  You may not use
5  * this file except in compliance with the License.  You can obtain a copy
6  * in the file LICENSE in the source distribution or at
7  * https://www.openssl.org/source/license.html
8  */
9
10 #include <openssl/bio.h>
11 #include "quictestlib.h"
12 #include "../testutil.h"
13
14 #define MSG_DATA_LEN_MAX    1472
15
16 struct noisy_dgram_st {
17     uint64_t this_dgram;
18     BIO_MSG msg;
19     uint64_t delayed_dgram;
20 };
21
22 static int noisy_dgram_read(BIO *bio, char *out, int outl)
23 {
24     /* We don't support this - not needed anyway */
25     return -1;
26 }
27
28 static int noisy_dgram_write(BIO *bio, const char *in, int inl)
29 {
30     /* We don't support this - not needed anyway */
31     return -1;
32 }
33
34 static long noisy_dgram_ctrl(BIO *bio, int cmd, long num, void *ptr)
35 {
36     long ret;
37     BIO *next = BIO_next(bio);
38
39     if (next == NULL)
40         return 0;
41
42     switch (cmd) {
43     case BIO_CTRL_DUP:
44         ret = 0L;
45         break;
46     default:
47         ret = BIO_ctrl(next, cmd, num, ptr);
48         break;
49     }
50     return ret;
51 }
52
53 static int noisy_dgram_gets(BIO *bio, char *buf, int size)
54 {
55     /* We don't support this - not needed anyway */
56     return -1;
57 }
58
59 static int noisy_dgram_puts(BIO *bio, const char *str)
60 {
61     /* We don't support this - not needed anyway */
62     return -1;
63 }
64
65 static int noisy_dgram_sendmmsg(BIO *bio, BIO_MSG *msg, size_t stride,
66                                 size_t num_msg, uint64_t flags,
67                                 size_t *msgs_processed)
68 {
69     BIO *next = BIO_next(bio);
70
71     if (next == NULL)
72         return 0;
73
74     /*
75      * We only introduce noise when receiving messages. We just pass this on
76      * to the underlying BIO.
77      */
78     return BIO_sendmmsg(next, msg, stride, num_msg, flags, msgs_processed);
79 }
80
81 static void get_noise(uint64_t *delay, int *should_drop)
82 {
83     uint32_t type;
84
85     /* 20% of all datagrams should be noisy */
86     if (test_random() % 5 != 0) {
87         *delay = 0;
88         *should_drop = 0;
89         return;
90     }
91
92     type = test_random() % 3;
93
94     /* Of noisy datagrams, 33% drop only, 33% delay only, 33% drop and delay */
95
96     *should_drop = (type == 0 || type == 1);
97
98     /* Where a delay occurs we delay by 1 - 4 datagrams */
99     *delay = (type == 0) ? 0 : (uint64_t)((test_random() % 4) + 1);
100
101     /*
102      * No point in delaying by 1 datagram if we are also dropping, so we delay
103      * by an extra datagram in that case
104      */
105     *delay += (uint64_t)(*should_drop);
106 }
107
108 static int noisy_dgram_recvmmsg(BIO *bio, BIO_MSG *msg, size_t stride,
109                                 size_t num_msg, uint64_t flags,
110                                 size_t *msgs_processed)
111 {
112     BIO *next = BIO_next(bio);
113     size_t i, j, data_len = 0, msg_cnt = 0;
114     BIO_MSG *thismsg;
115     struct noisy_dgram_st *data;
116
117     if (!TEST_ptr(next))
118         return 0;
119
120     data = BIO_get_data(bio);
121     if (!TEST_ptr(data))
122         return 0;
123
124     /*
125      * For simplicity we assume that all elements in the msg array have the
126      * same data_len. They are not required to by the API, but it would be quite
127      * strange for that not to be the case - and our code that calls
128      * BIO_recvmmsg does do this (which is all that is important for this test
129      * code). We test the invariant here.
130      */
131     for (i = 0; i < num_msg; i++) {
132         if (i == 0) {
133             data_len = msg[i].data_len;
134             if (!TEST_size_t_le(data_len, MSG_DATA_LEN_MAX))
135                 return 0;
136         } else if (!TEST_size_t_eq(msg[i].data_len, data_len)) {
137             return 0;
138         }
139     }
140
141     if (!BIO_recvmmsg(next, msg, stride, num_msg, flags, msgs_processed))
142         return 0;
143
144 #ifdef OSSL_NOISY_DGRAM_DEBUG
145     printf("Pre-filter datagram list:\n");
146     for (i = 0; i < *msgs_processed; i++) {
147         printf("Pre-filter Datagram:\n");
148         BIO_dump_fp(stdout, msg[i].data, msg[i].data_len);
149         printf("\n");
150     }
151     printf("End of pre-filter datagram list\nApplying noise filters:\n");
152 #endif
153
154     msg_cnt = *msgs_processed;
155
156     /* Introduce noise */
157     for (i = 0, thismsg = msg;
158          i < msg_cnt;
159          i++, thismsg++, data->this_dgram++) {
160         uint64_t delay;
161         int should_drop;
162
163         /* If we have a delayed message ready insert it now */
164         if (data->delayed_dgram > 0
165                 && data->delayed_dgram == data->this_dgram) {
166             if (msg_cnt < num_msg) {
167                 /* Make space for the inserted message */
168                 for (j = msg_cnt; j > i; j--) {
169                     if (!bio_msg_copy(&msg[j], &msg[j - 1]))
170                         return 0;
171                 }
172                 if (!bio_msg_copy(thismsg, &data->msg))
173                     return 0;
174                 msg_cnt++;
175                 data->delayed_dgram = 0;
176 #ifdef OSSL_NOISY_DGRAM_DEBUG
177                 printf("**Inserting a delayed datagram\n");
178                 BIO_dump_fp(stdout, thismsg->data, thismsg->data_len);
179                 printf("\n");
180 #endif
181                 continue;
182             } /* else we have no space for the insertion, so just drop it */
183             data->delayed_dgram = 0;
184         }
185
186         get_noise(&delay, &should_drop);
187
188         /* We ignore delay if a message is already delayed */
189         if (delay > 0 && data->delayed_dgram == 0) {
190             /*
191              * Note that a message may be delayed *and* dropped, or delayed
192              * and *not* dropped.
193              * Delayed and dropped means the message will not be sent now and
194              * will only be sent after the delay.
195              * Delayed and not dropped means the message will be sent now and
196              * a duplicate will also be sent after the delay.
197              */
198
199             if (!bio_msg_copy(&data->msg, thismsg))
200                 return 0;
201
202             data->delayed_dgram = data->this_dgram + delay;
203
204 #ifdef OSSL_NOISY_DGRAM_DEBUG
205             printf("**Delaying a datagram for %u messages%s\n",
206                    (unsigned int)delay, should_drop ? "" : "(duplicating)");
207             BIO_dump_fp(stdout, thismsg->data, thismsg->data_len);
208             printf("\n");
209 #endif
210         }
211
212         if (should_drop) {
213 #ifdef OSSL_NOISY_DGRAM_DEBUG
214             printf("**Dropping a datagram\n");
215             BIO_dump_fp(stdout, thismsg->data, thismsg->data_len);
216             printf("\n");
217 #endif
218             for (j = i + 1; j < msg_cnt; j++) {
219                 if (!bio_msg_copy(&msg[j - 1], &msg[j]))
220                     return 0;
221             }
222             msg_cnt--;
223         }
224     }
225
226 #ifdef OSSL_NOISY_DGRAM_DEBUG
227     printf("End of noise filters\nPost-filter datagram list:\n");
228     for (i = 0; i < msg_cnt; i++) {
229         printf("Post-filter Datagram:\n");
230         BIO_dump_fp(stdout, msg[i].data, msg[i].data_len);
231         printf("\n");
232     }
233     printf("End of post-filter datagram list\n");
234 #endif
235
236     *msgs_processed = msg_cnt;
237
238     if (msg_cnt == 0) {
239         ERR_raise(ERR_LIB_BIO, BIO_R_NON_FATAL);
240         return 0;
241     }
242
243     return 1;
244 }
245
246 static void data_free(struct noisy_dgram_st *data)
247 {
248     if (data == NULL)
249         return;
250
251     OPENSSL_free(data->msg.data);
252     BIO_ADDR_free(data->msg.peer);
253     BIO_ADDR_free(data->msg.local);
254     OPENSSL_free(data);
255 }
256
257 static int noisy_dgram_new(BIO *bio)
258 {
259     struct noisy_dgram_st *data = OPENSSL_zalloc(sizeof(*data));
260
261     if (!TEST_ptr(data))
262         return 0;
263
264     data->msg.data = OPENSSL_malloc(MSG_DATA_LEN_MAX);
265     data->msg.peer = BIO_ADDR_new();
266     data->msg.local = BIO_ADDR_new();
267     if (data->msg.data == NULL
268             || data->msg.peer == NULL
269             || data->msg.local == NULL) {
270         data_free(data);
271         return 0;
272     }
273
274     BIO_set_data(bio, data);
275     BIO_set_init(bio, 1);
276
277     return 1;
278 }
279
280 static int noisy_dgram_free(BIO *bio)
281 {
282     data_free(BIO_get_data(bio));
283     BIO_set_data(bio, NULL);
284     BIO_set_init(bio, 0);
285
286     return 1;
287 }
288
289 /* Choose a sufficiently large type likely to be unused for this custom BIO */
290 #define BIO_TYPE_NOISY_DGRAM_FILTER  (0x80 | BIO_TYPE_FILTER)
291
292 static BIO_METHOD *method_noisy_dgram = NULL;
293
294 /* Note: Not thread safe! */
295 const BIO_METHOD *bio_f_noisy_dgram_filter(void)
296 {
297     if (method_noisy_dgram == NULL) {
298         method_noisy_dgram = BIO_meth_new(BIO_TYPE_NOISY_DGRAM_FILTER,
299                                           "Nosiy datagram filter");
300         if (method_noisy_dgram == NULL
301             || !BIO_meth_set_write(method_noisy_dgram, noisy_dgram_write)
302             || !BIO_meth_set_read(method_noisy_dgram, noisy_dgram_read)
303             || !BIO_meth_set_puts(method_noisy_dgram, noisy_dgram_puts)
304             || !BIO_meth_set_gets(method_noisy_dgram, noisy_dgram_gets)
305             || !BIO_meth_set_ctrl(method_noisy_dgram, noisy_dgram_ctrl)
306             || !BIO_meth_set_sendmmsg(method_noisy_dgram, noisy_dgram_sendmmsg)
307             || !BIO_meth_set_recvmmsg(method_noisy_dgram, noisy_dgram_recvmmsg)
308             || !BIO_meth_set_create(method_noisy_dgram, noisy_dgram_new)
309             || !BIO_meth_set_destroy(method_noisy_dgram, noisy_dgram_free))
310             return NULL;
311     }
312     return method_noisy_dgram;
313 }
314
315 void bio_f_noisy_dgram_filter_free(void)
316 {
317     BIO_meth_free(method_noisy_dgram);
318 }