b42dbe6f4b50e12f1bf5bbd3f2f2f3df5c6b8b60
[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 /* There isn't a public function to do BIO_ADDR_copy() so we create one */
109 static int bio_addr_copy(BIO_ADDR *dst, BIO_ADDR *src)
110 {
111     size_t len;
112     void *data = NULL;
113     int res = 0;
114     int family;
115
116     if (src == NULL || dst == NULL)
117         return 0;
118
119     family = BIO_ADDR_family(src);
120     if (family == AF_UNSPEC) {
121         BIO_ADDR_clear(dst);
122         return 1;
123     }
124
125     if (!BIO_ADDR_rawaddress(src, NULL, &len))
126         return 0;
127
128     if (len > 0) {
129         data = OPENSSL_malloc(len);
130         if (!TEST_ptr(data))
131             return 0;
132     }
133
134     if (!BIO_ADDR_rawaddress(src, data, &len))
135         goto err;
136
137     if (!BIO_ADDR_rawmake(src, family, data, len, BIO_ADDR_rawport(src)))
138         goto err;
139
140     res = 1;
141  err:
142     OPENSSL_free(data);
143     return res;
144 }
145
146 static int bio_msg_copy(BIO_MSG *dst, BIO_MSG *src)
147 {
148     /*
149      * Note it is assumed that the originally allocated data sizes for dst and
150      * src are the same
151      */
152     memcpy(dst->data, src->data, src->data_len);
153     dst->data_len = src->data_len;
154     dst->flags = src->flags;
155     if (dst->local != NULL) {
156         if (src->local != NULL) {
157             if (!TEST_true(bio_addr_copy(dst->local, src->local)))
158                 return 0;
159         } else {
160             BIO_ADDR_clear(dst->local);
161         }
162     }
163     if (!TEST_true(bio_addr_copy(dst->peer, src->peer)))
164         return 0;
165
166     return 1;
167 }
168
169 static int noisy_dgram_recvmmsg(BIO *bio, BIO_MSG *msg, size_t stride,
170                                 size_t num_msg, uint64_t flags,
171                                 size_t *msgs_processed)
172 {
173     BIO *next = BIO_next(bio);
174     size_t i, j, data_len = 0, msg_cnt = 0;
175     BIO_MSG *thismsg;
176     struct noisy_dgram_st *data;
177
178     if (!TEST_ptr(next))
179         return 0;
180
181     data = BIO_get_data(bio);
182     if (!TEST_ptr(data))
183         return 0;
184
185     /*
186      * For simplicity we assume that all elements in the msg array have the
187      * same data_len. They are not required to by the API, but it would be quite
188      * strange for that not to be the case - and our code that calls
189      * BIO_recvmmsg does do this (which is all that is important for this test
190      * code). We test the invariant here.
191      */
192     for (i = 0; i < num_msg; i++) {
193         if (i == 0) {
194             data_len = msg[i].data_len;
195             if (!TEST_size_t_le(data_len, MSG_DATA_LEN_MAX))
196                 return 0;
197         } else if (!TEST_size_t_eq(msg[i].data_len, data_len)) {
198             return 0;
199         }
200     }
201
202     if (!BIO_recvmmsg(next, msg, stride, num_msg, flags, msgs_processed))
203         return 0;
204
205 #ifdef OSSL_NOISY_DGRAM_DEBUG
206     printf("Pre-filter datagram list:\n");
207     for (i = 0; i < *msgs_processed; i++) {
208         printf("Pre-filter Datagram:\n");
209         BIO_dump_fp(stdout, msg[i].data, msg[i].data_len);
210         printf("\n");
211     }
212     printf("End of pre-filter datagram list\nApplying noise filters:\n");
213 #endif
214
215     msg_cnt = *msgs_processed;
216
217     /* Introduce noise */
218     for (i = 0, thismsg = msg;
219          i < msg_cnt;
220          i++, thismsg++, data->this_dgram++) {
221         uint64_t delay;
222         int should_drop;
223
224         /* If we have a delayed message ready insert it now */
225         if (data->delayed_dgram > 0
226                 && data->delayed_dgram == data->this_dgram) {
227             if (msg_cnt < num_msg) {
228                 /* Make space for the inserted message */
229                 for (j = msg_cnt; j > i; j--) {
230                     if (!bio_msg_copy(&msg[j], &msg[j - 1]))
231                         return 0;
232                 }
233                 if (!bio_msg_copy(thismsg, &data->msg))
234                     return 0;
235                 msg_cnt++;
236                 data->delayed_dgram = 0;
237 #ifdef OSSL_NOISY_DGRAM_DEBUG
238                 printf("**Inserting a delayed datagram\n");
239                 BIO_dump_fp(stdout, thismsg->data, thismsg->data_len);
240                 printf("\n");
241 #endif
242                 continue;
243             } /* else we have no space for the insertion, so just drop it */
244             data->delayed_dgram = 0;
245         }
246
247         get_noise(&delay, &should_drop);
248
249         /* We ignore delay if a message is already delayed */
250         if (delay > 0 && data->delayed_dgram == 0) {
251             /*
252              * Note that a message may be delayed *and* dropped, or delayed
253              * and *not* dropped.
254              * Delayed and dropped means the message will not be sent now and
255              * will only be sent after the delay.
256              * Delayed and not dropped means the message will be sent now and
257              * a duplicate will also be sent after the delay.
258              */
259
260             if (!bio_msg_copy(&data->msg, thismsg))
261                 return 0;
262
263             data->delayed_dgram = data->this_dgram + delay;
264
265 #ifdef OSSL_NOISY_DGRAM_DEBUG
266             printf("**Delaying a datagram for %u messages%s\n",
267                    (unsigned int)delay, should_drop ? "" : "(duplicating)");
268             BIO_dump_fp(stdout, thismsg->data, thismsg->data_len);
269             printf("\n");
270 #endif
271         }
272
273         if (should_drop) {
274 #ifdef OSSL_NOISY_DGRAM_DEBUG
275             printf("**Dropping a datagram\n");
276             BIO_dump_fp(stdout, thismsg->data, thismsg->data_len);
277             printf("\n");
278 #endif
279             for (j = i + 1; j < msg_cnt; j++) {
280                 if (!bio_msg_copy(&msg[j - 1], &msg[j]))
281                     return 0;
282             }
283             msg_cnt--;
284         }
285     }
286
287 #ifdef OSSL_NOISY_DGRAM_DEBUG
288     printf("End of noise filters\nPost-filter datagram list:\n");
289     for (i = 0; i < msg_cnt; i++) {
290         printf("Post-filter Datagram:\n");
291         BIO_dump_fp(stdout, msg[i].data, msg[i].data_len);
292         printf("\n");
293     }
294     printf("End of post-filter datagram list\n");
295 #endif
296
297     *msgs_processed = msg_cnt;
298
299     if (msg_cnt == 0) {
300         ERR_raise(ERR_LIB_BIO, BIO_R_NON_FATAL);
301         return 0;
302     }
303
304     return 1;
305 }
306
307 static void data_free(struct noisy_dgram_st *data)
308 {
309     if (data == NULL)
310         return;
311
312     OPENSSL_free(data->msg.data);
313     BIO_ADDR_free(data->msg.peer);
314     BIO_ADDR_free(data->msg.local);
315     OPENSSL_free(data);
316 }
317
318 static int noisy_dgram_new(BIO *bio)
319 {
320     struct noisy_dgram_st *data = OPENSSL_zalloc(sizeof(*data));
321
322     if (!TEST_ptr(data))
323         return 0;
324
325     data->msg.data = OPENSSL_malloc(MSG_DATA_LEN_MAX);
326     data->msg.peer = BIO_ADDR_new();
327     data->msg.local = BIO_ADDR_new();
328     if (data->msg.data == NULL
329             || data->msg.peer == NULL
330             || data->msg.local == NULL) {
331         data_free(data);
332         return 0;
333     }
334
335     BIO_set_data(bio, data);
336     BIO_set_init(bio, 1);
337
338     return 1;
339 }
340
341 static int noisy_dgram_free(BIO *bio)
342 {
343     data_free(BIO_get_data(bio));
344     BIO_set_data(bio, NULL);
345     BIO_set_init(bio, 0);
346
347     return 1;
348 }
349
350 /* Choose a sufficiently large type likely to be unused for this custom BIO */
351 #define BIO_TYPE_NOISY_DGRAM_FILTER  (0x80 | BIO_TYPE_FILTER)
352
353 static BIO_METHOD *method_noisy_dgram = NULL;
354
355 /* Note: Not thread safe! */
356 const BIO_METHOD *bio_f_noisy_dgram_filter(void)
357 {
358     if (method_noisy_dgram == NULL) {
359         method_noisy_dgram = BIO_meth_new(BIO_TYPE_NOISY_DGRAM_FILTER,
360                                           "Nosiy datagram filter");
361         if (method_noisy_dgram == NULL
362             || !BIO_meth_set_write(method_noisy_dgram, noisy_dgram_write)
363             || !BIO_meth_set_read(method_noisy_dgram, noisy_dgram_read)
364             || !BIO_meth_set_puts(method_noisy_dgram, noisy_dgram_puts)
365             || !BIO_meth_set_gets(method_noisy_dgram, noisy_dgram_gets)
366             || !BIO_meth_set_ctrl(method_noisy_dgram, noisy_dgram_ctrl)
367             || !BIO_meth_set_sendmmsg(method_noisy_dgram, noisy_dgram_sendmmsg)
368             || !BIO_meth_set_recvmmsg(method_noisy_dgram, noisy_dgram_recvmmsg)
369             || !BIO_meth_set_create(method_noisy_dgram, noisy_dgram_new)
370             || !BIO_meth_set_destroy(method_noisy_dgram, noisy_dgram_free))
371             return NULL;
372     }
373     return method_noisy_dgram;
374 }
375
376 void bio_f_noisy_dgram_filter_free(void)
377 {
378     BIO_meth_free(method_noisy_dgram);
379 }