Coverage Report

Created: 2026-09-28 07:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata8/src/stream-tcp-sack.c
Line
Count
Source
1
/* Copyright (C) 2007-2011 Open Information Security Foundation
2
 *
3
 * You can copy, redistribute or modify this Program under the terms of
4
 * the GNU General Public License version 2 as published by the Free
5
 * Software Foundation.
6
 *
7
 * This program 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
10
 * GNU General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * version 2 along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15
 * 02110-1301, USA.
16
 */
17
18
/**
19
 * \file
20
 *
21
 * \author Victor Julien <victor@inliniac.net>
22
 *
23
 * Stream engine TCP SACK handling.
24
 */
25
26
#include "suricata-common.h"
27
#include "stream-tcp.h"
28
#include "stream-tcp-private.h"
29
#include "stream-tcp-sack.h"
30
#include "util-unittest.h"
31
32
3.86M
RB_GENERATE(TCPSACK, StreamTcpSackRecord, rb, TcpSackCompare);
TCPSACK_RB_INSERT_COLOR
Line
Count
Source
32
RB_GENERATE(TCPSACK, StreamTcpSackRecord, rb, TcpSackCompare);
TCPSACK_RB_REMOVE_COLOR
Line
Count
Source
32
RB_GENERATE(TCPSACK, StreamTcpSackRecord, rb, TcpSackCompare);
TCPSACK_RB_INSERT
Line
Count
Source
32
RB_GENERATE(TCPSACK, StreamTcpSackRecord, rb, TcpSackCompare);
TCPSACK_RB_REMOVE
Line
Count
Source
32
RB_GENERATE(TCPSACK, StreamTcpSackRecord, rb, TcpSackCompare);
Unexecuted instantiation: TCPSACK_RB_FIND
Unexecuted instantiation: TCPSACK_RB_NFIND
TCPSACK_RB_MINMAX
Line
Count
Source
32
RB_GENERATE(TCPSACK, StreamTcpSackRecord, rb, TcpSackCompare);
33
3.86M
34
3.86M
int TcpSackCompare(struct StreamTcpSackRecord *a, struct StreamTcpSackRecord *b)
35
3.86M
{
36
50.3k
    if (SEQ_GT(a->le, b->le))
37
15.9k
        return 1;
38
34.3k
    else if (SEQ_LT(a->le, b->le))
39
16.3k
        return -1;
40
17.9k
    else {
41
17.9k
        if (SEQ_EQ(a->re, b->re))
42
9.32k
            return 0;
43
8.67k
        else if (SEQ_GT(a->re, b->re))
44
1.77k
            return 1;
45
6.89k
        else
46
6.89k
            return -1;
47
17.9k
    }
48
50.3k
}
49
#ifdef DEBUG
50
static void StreamTcpSackPrintList(TcpStream *stream)
51
{
52
    SCLogDebug("size %u", stream->sack_size);
53
    StreamTcpSackRecord *rec = NULL;
54
    RB_FOREACH(rec, TCPSACK, &stream->sack_tree) {
55
        SCLogDebug("- record %8u - %8u", rec->le, rec->re);
56
    }
57
}
58
#endif /* DEBUG */
59
60
static inline StreamTcpSackRecord *StreamTcpSackRecordAlloc(void)
61
28.0k
{
62
28.0k
    if (StreamTcpCheckMemcap((uint32_t)sizeof(StreamTcpSackRecord)) == 0)
63
0
        return NULL;
64
65
28.0k
    StreamTcpSackRecord *rec = SCMalloc(sizeof(*rec));
66
28.0k
    if (unlikely(rec == NULL))
67
0
        return NULL;
68
69
28.0k
    StreamTcpIncrMemuse((uint64_t)sizeof(*rec));
70
28.0k
    return rec;
71
28.0k
}
72
73
static inline void StreamTcpSackRecordFree(StreamTcpSackRecord *rec)
74
28.0k
{
75
28.0k
    SCFree(rec);
76
28.0k
    StreamTcpDecrMemuse((uint64_t)sizeof(*rec));
77
28.0k
}
78
79
static inline void ConsolidateFwd(TcpStream *stream, struct TCPSACK *tree, struct StreamTcpSackRecord *sa)
80
18.7k
{
81
18.7k
    struct StreamTcpSackRecord *tr, *s = sa;
82
38.3k
    RB_FOREACH_FROM(tr, TCPSACK, s) {
83
38.3k
        if (sa == tr)
84
18.7k
            continue;
85
19.6k
        SCLogDebug("-> (fwd) tr %p %u/%u", tr, tr->le, tr->re);
86
87
19.6k
        if (SEQ_LT(sa->re, tr->le))
88
9.59k
            break; // entirely before
89
90
10.0k
        if (SEQ_GEQ(sa->le, tr->le) && SEQ_LEQ(sa->re, tr->re)) {
91
6.89k
            stream->sack_size -= (tr->re - tr->le);
92
6.89k
            stream->sack_size -= (sa->re - sa->le);
93
6.89k
            sa->re = tr->re;
94
6.89k
            sa->le = tr->le;
95
6.89k
            stream->sack_size += (sa->re - sa->le);
96
6.89k
            SCLogDebug("-> (fwd) tr %p %u/%u REMOVED ECLIPSED2", tr, tr->le, tr->re);
97
6.89k
            TCPSACK_RB_REMOVE(tree, tr);
98
6.89k
            StreamTcpSackRecordFree(tr);
99
        /*
100
            sa: [         ]
101
            tr: [         ]
102
            sa: [         ]
103
            tr:    [      ]
104
            sa: [         ]
105
            tr:    [   ]
106
        */
107
6.89k
        } else if (SEQ_LEQ(sa->le, tr->le) && SEQ_GEQ(sa->re, tr->re)) {
108
2.31k
            SCLogDebug("-> (fwd) tr %p %u/%u REMOVED ECLIPSED", tr, tr->le, tr->re);
109
2.31k
            stream->sack_size -= (tr->re - tr->le);
110
2.31k
            TCPSACK_RB_REMOVE(tree, tr);
111
2.31k
            StreamTcpSackRecordFree(tr);
112
        /*
113
            sa: [         ]
114
            tr:      [         ]
115
            sa: [       ]
116
            tr:         [       ]
117
        */
118
2.31k
        } else if (SEQ_LT(sa->le, tr->le) && // starts before
119
795
                   SEQ_GEQ(sa->re, tr->le) && SEQ_LT(sa->re, tr->re) // ends inside
120
795
            ) {
121
            // merge
122
795
            stream->sack_size -= (tr->re - tr->le);
123
795
            stream->sack_size -= (sa->re - sa->le);
124
795
            sa->re = tr->re;
125
795
            stream->sack_size += (sa->re - sa->le);
126
795
            SCLogDebug("-> (fwd) tr %p %u/%u REMOVED MERGED", tr, tr->le, tr->re);
127
795
            TCPSACK_RB_REMOVE(tree, tr);
128
795
            StreamTcpSackRecordFree(tr);
129
795
        }
130
10.0k
    }
131
18.7k
}
132
133
static inline void ConsolidateBackward(TcpStream *stream,
134
        struct TCPSACK *tree, struct StreamTcpSackRecord *sa)
135
18.7k
{
136
18.7k
    struct StreamTcpSackRecord *tr, *s = sa;
137
32.5k
    RB_FOREACH_REVERSE_FROM(tr, TCPSACK, s) {
138
32.5k
        if (sa == tr)
139
18.7k
            continue;
140
13.7k
        SCLogDebug("-> (bwd) tr %p %u/%u", tr, tr->le, tr->re);
141
142
13.7k
        if (SEQ_GT(sa->le, tr->re))
143
8.22k
            break; // entirely after
144
5.57k
        if (SEQ_GEQ(sa->le, tr->le) && SEQ_LEQ(sa->re, tr->re)) {
145
3.71k
            stream->sack_size -= (tr->re - tr->le);
146
3.71k
            stream->sack_size -= (sa->re - sa->le);
147
3.71k
            sa->re = tr->re;
148
3.71k
            sa->le = tr->le;
149
3.71k
            stream->sack_size += (sa->re - sa->le);
150
3.71k
            SCLogDebug("-> (bwd) tr %p %u/%u REMOVED ECLIPSED2", tr, tr->le, tr->re);
151
3.71k
            TCPSACK_RB_REMOVE(tree, tr);
152
3.71k
            StreamTcpSackRecordFree(tr);
153
        /*
154
            sa: [         ]
155
            tr: [         ]
156
            sa:    [      ]
157
            tr: [         ]
158
            sa:    [   ]
159
            tr: [         ]
160
        */
161
3.71k
        } else if (SEQ_LEQ(sa->le, tr->le) && SEQ_GEQ(sa->re, tr->re)) {
162
1.77k
            SCLogDebug("-> (bwd) tr %p %u/%u REMOVED ECLIPSED", tr, tr->le, tr->re);
163
1.77k
            stream->sack_size -= (tr->re - tr->le);
164
1.77k
            TCPSACK_RB_REMOVE(tree, tr);
165
1.77k
            StreamTcpSackRecordFree(tr);
166
        /*
167
            sa:     [   ]
168
            tr: [   ]
169
            sa:    [    ]
170
            tr: [   ]
171
        */
172
1.77k
        } else if (SEQ_GT(sa->le, tr->le) && SEQ_GT(sa->re, tr->re) && SEQ_LEQ(sa->le,tr->re)) {
173
            // merge
174
83
            stream->sack_size -= (tr->re - tr->le);
175
83
            stream->sack_size -= (sa->re - sa->le);
176
83
            sa->le = tr->le;
177
83
            stream->sack_size += (sa->re - sa->le);
178
83
            SCLogDebug("-> (bwd) tr %p %u/%u REMOVED MERGED", tr, tr->le, tr->re);
179
83
            TCPSACK_RB_REMOVE(tree, tr);
180
83
            StreamTcpSackRecordFree(tr);
181
83
        }
182
5.57k
    }
183
18.7k
}
184
185
static int Insert(TcpStream *stream, struct TCPSACK *tree, uint32_t le, uint32_t re)
186
28.0k
{
187
28.0k
    SCLogDebug("inserting: %u-%u", le, re);
188
189
28.0k
    struct StreamTcpSackRecord *sa = StreamTcpSackRecordAlloc();
190
28.0k
    if (unlikely(sa == NULL))
191
0
        return -1;
192
28.0k
    sa->le = le;
193
28.0k
    sa->re = re;
194
28.0k
    struct StreamTcpSackRecord *res = TCPSACK_RB_INSERT(tree, sa);
195
28.0k
    if (res) {
196
        // exact overlap
197
9.32k
        SCLogDebug("* insert failed: exact match in tree with %p %u/%u", res, res->le, res->re);
198
9.32k
        StreamTcpSackRecordFree(sa);
199
9.32k
        return 0;
200
9.32k
    }
201
18.7k
    stream->sack_size += (re - le);
202
18.7k
    ConsolidateBackward(stream, tree, sa);
203
18.7k
    ConsolidateFwd(stream, tree, sa);
204
18.7k
    return 0;
205
28.0k
}
206
207
/**
208
 *  \brief insert a SACK range
209
 *
210
 *  \param le left edge in host order
211
 *  \param re right edge in host order
212
 *
213
 *  \retval 0 all is good
214
 *  \retval -1 error
215
 */
216
static int StreamTcpSackInsertRange(TcpStream *stream, uint32_t le, uint32_t re)
217
28.0k
{
218
28.0k
    SCLogDebug("le %u, re %u", le, re);
219
#ifdef DEBUG
220
    StreamTcpSackPrintList(stream);
221
#endif
222
223
    /* if to the left of last_ack then ignore */
224
28.0k
    if (SEQ_LT(re, stream->last_ack)) {
225
0
        SCLogDebug("too far left. discarding");
226
0
        SCReturnInt(0);
227
0
    }
228
    /* if to the right of the tcp window then ignore */
229
28.0k
    if (SEQ_GT(le, (stream->last_ack + stream->window))) {
230
32
        SCLogDebug("too far right. discarding");
231
32
        SCReturnInt(0);
232
32
    }
233
234
28.0k
    if (Insert(stream, &stream->sack_tree, le, re) < 0)
235
0
        SCReturnInt(-1);
236
237
28.0k
    SCReturnInt(0);
238
28.0k
}
239
240
/**
241
 *  \brief Update stream with SACK records from a TCP packet.
242
 *
243
 *  \param stream The stream to update.
244
 *  \param p packet to get the SACK records from
245
 *
246
 *  \retval -1 error
247
 *  \retval 0 ok
248
 */
249
int StreamTcpSackUpdatePacket(TcpStream *stream, Packet *p)
250
6.18M
{
251
6.18M
    SCEnter();
252
253
6.18M
    const TCPHdr *tcph = PacketGetTCP(p);
254
6.18M
    const int records = TCP_GET_SACK_CNT(p);
255
6.18M
    const uint8_t *data = TCP_GET_SACK_PTR(p, tcph);
256
257
6.18M
    if (records == 0 || data == NULL)
258
6.15M
        SCReturnInt(0);
259
260
31.2k
    TCPOptSackRecord rec[records], *sack_rec = rec;
261
31.2k
    memcpy(&rec, data, sizeof(TCPOptSackRecord) * records);
262
263
31.2k
    uint32_t first_le = 0;
264
31.2k
    uint32_t first_re = 0;
265
266
91.1k
    for (int record = 0; record < records; record++) {
267
59.9k
        const uint32_t le = SCNtohl(sack_rec->le);
268
59.9k
        const uint32_t re = SCNtohl(sack_rec->re);
269
270
59.9k
        if (!first_le)
271
31.2k
            first_le = le;
272
59.9k
        if (!first_re)
273
31.2k
            first_re = re;
274
275
59.9k
        SCLogDebug("%p last_ack %u, left edge %u, right edge %u pkt ACK %u", sack_rec,
276
59.9k
                stream->last_ack, le, re, TCP_GET_RAW_ACK(tcph));
277
278
        /* RFC 2883 D-SACK */
279
59.9k
        if (SEQ_LT(le, TCP_GET_RAW_ACK(tcph))) {
280
4.02k
            SCLogDebug("packet: %" PRIu64 ": D-SACK? %u-%u before ACK %u", p->pcap_cnt, le, re,
281
4.02k
                    TCP_GET_RAW_ACK(tcph));
282
4.02k
            STREAM_PKT_FLAG_SET(p, STREAM_PKT_FLAG_DSACK);
283
4.02k
            goto next;
284
55.8k
        } else if (record == 1) { // 2nd record
285
18.6k
            if (SEQ_GEQ(first_le, le) && SEQ_LEQ(first_re, re)) {
286
991
                SCLogDebug("packet: %" PRIu64 ": D-SACK? %u-%u inside 2nd range %u-%u ACK %u",
287
991
                        p->pcap_cnt, first_le, first_re, le, re, TCP_GET_RAW_ACK(tcph));
288
991
                STREAM_PKT_FLAG_SET(p, STREAM_PKT_FLAG_DSACK);
289
991
            }
290
18.6k
            goto next;
291
18.6k
        }
292
293
37.2k
        if (SEQ_LEQ(re, stream->last_ack)) {
294
7.27k
            SCLogDebug("record before last_ack");
295
7.27k
            goto next;
296
7.27k
        }
297
298
29.9k
        if (SEQ_GT(re, stream->next_win)) {
299
1.54k
            SCLogDebug("record %u:%u beyond next_win %u",
300
1.54k
                    le, re, stream->next_win);
301
1.54k
            goto next;
302
1.54k
        }
303
304
28.4k
        if (SEQ_GEQ(le, re)) {
305
345
            SCLogDebug("invalid record: le >= re");
306
345
            goto next;
307
345
        }
308
309
28.0k
        if (StreamTcpSackInsertRange(stream, le, re) == -1) {
310
0
            SCReturnInt(-1);
311
0
        }
312
313
59.9k
    next:
314
59.9k
        sack_rec++;
315
59.9k
    }
316
31.2k
    StreamTcpSackPruneList(stream);
317
#ifdef DEBUG
318
    StreamTcpSackPrintList(stream);
319
#endif
320
31.2k
    SCReturnInt(0);
321
31.2k
}
322
323
static inline int CompareOverlap(
324
        struct StreamTcpSackRecord *lookup, struct StreamTcpSackRecord *intree)
325
29.2k
{
326
29.2k
    if (lookup->re <= intree->le) // entirely before
327
12.2k
        return -1;
328
17.0k
    else if (lookup->re >= intree->le && lookup->le < intree->re) // (some) overlap
329
10.3k
        return 0;
330
6.61k
    else
331
6.61k
        return 1; // entirely after
332
29.2k
}
333
334
static struct StreamTcpSackRecord *FindOverlap(
335
        struct TCPSACK *head, struct StreamTcpSackRecord *elm)
336
21.5k
{
337
21.5k
    SCLogDebug("looking up le:%u re:%u", elm->le, elm->re);
338
339
21.5k
    struct StreamTcpSackRecord *tmp = RB_ROOT(head);
340
21.5k
    struct StreamTcpSackRecord *res = NULL;
341
40.3k
    while (tmp) {
342
29.2k
        SCLogDebug("compare with le:%u re:%u", tmp->le, tmp->re);
343
29.2k
        const int comp = CompareOverlap(elm, tmp);
344
29.2k
        SCLogDebug("compare result: %d", comp);
345
29.2k
        if (comp < 0) {
346
12.2k
            res = tmp;
347
12.2k
            tmp = RB_LEFT(tmp, rb);
348
17.0k
        } else if (comp > 0) {
349
6.61k
            tmp = RB_RIGHT(tmp, rb);
350
10.3k
        } else {
351
10.3k
            return tmp;
352
10.3k
        }
353
29.2k
    }
354
11.1k
    return res;
355
21.5k
}
356
357
bool StreamTcpSackPacketIsOutdated(TcpStream *stream, Packet *p)
358
10.6k
{
359
10.6k
    const TCPHdr *tcph = PacketGetTCP(p);
360
10.6k
    const int records = TCP_GET_SACK_CNT(p);
361
10.6k
    const uint8_t *data = TCP_GET_SACK_PTR(p, tcph);
362
10.6k
    if (records > 0 && data != NULL) {
363
10.6k
        int sack_outdated = 0;
364
10.6k
        TCPOptSackRecord rec[records], *sack_rec = rec;
365
10.6k
        memcpy(&rec, data, sizeof(TCPOptSackRecord) * records);
366
32.1k
        for (int record = 0; record < records; record++) {
367
21.5k
            const uint32_t le = SCNtohl(sack_rec->le);
368
21.5k
            const uint32_t re = SCNtohl(sack_rec->re);
369
21.5k
            SCLogDebug("%p last_ack %u, left edge %u, right edge %u", sack_rec, stream->last_ack,
370
21.5k
                    le, re);
371
372
21.5k
            struct StreamTcpSackRecord lookup = { .le = le, .re = re };
373
21.5k
            struct StreamTcpSackRecord *res = FindOverlap(&stream->sack_tree, &lookup);
374
21.5k
            SCLogDebug("res %p", res);
375
21.5k
            if (res) {
376
17.0k
                if (le >= res->le && re <= res->re) {
377
7.00k
                    SCLogDebug("SACK rec le:%u re:%u eclipsed by in tree le:%u re:%u", le, re,
378
7.00k
                            res->le, res->re);
379
7.00k
                    sack_outdated++;
380
10.0k
                } else {
381
10.0k
                    SCLogDebug("SACK rec le:%u re:%u SACKs new DATA vs in tree le:%u re:%u", le, re,
382
10.0k
                            res->le, res->re);
383
10.0k
                }
384
17.0k
            } else {
385
4.46k
                SCLogDebug("SACK rec le:%u re:%u SACKs new DATA. No match in tree", le, re);
386
4.46k
            }
387
21.5k
            sack_rec++;
388
21.5k
        }
389
#ifdef DEBUG
390
        StreamTcpSackPrintList(stream);
391
#endif
392
10.6k
        if (records != sack_outdated) {
393
            // SACK tree needs updating
394
9.75k
            return false;
395
9.75k
        } else {
396
            // SACK list is packet is completely outdated
397
853
            return true;
398
853
        }
399
10.6k
    }
400
0
    return false;
401
10.6k
}
402
403
void StreamTcpSackPruneList(TcpStream *stream)
404
3.06M
{
405
3.06M
    SCEnter();
406
407
3.06M
    StreamTcpSackRecord *rec = NULL, *safe = NULL;
408
3.06M
    RB_FOREACH_SAFE(rec, TCPSACK, &stream->sack_tree, safe) {
409
32.4k
        if (SEQ_LT(rec->re, stream->last_ack)) {
410
1.59k
            SCLogDebug("removing le %u re %u", rec->le, rec->re);
411
1.59k
            stream->sack_size -= (rec->re - rec->le);
412
1.59k
            TCPSACK_RB_REMOVE(&stream->sack_tree, rec);
413
1.59k
            StreamTcpSackRecordFree(rec);
414
415
30.8k
        } else if (SEQ_LT(rec->le, stream->last_ack)) {
416
4.61k
            SCLogDebug("adjusting record to le %u re %u", rec->le, rec->re);
417
            /* last ack inside this record, update */
418
4.61k
            stream->sack_size -= (rec->re - rec->le);
419
4.61k
            rec->le = stream->last_ack;
420
4.61k
            stream->sack_size += (rec->re - rec->le);
421
4.61k
            break;
422
26.2k
        } else {
423
26.2k
            SCLogDebug("record beyond last_ack, nothing to do. Bailing out.");
424
26.2k
            break;
425
26.2k
        }
426
32.4k
    }
427
#ifdef DEBUG
428
    StreamTcpSackPrintList(stream);
429
#endif
430
3.06M
    SCReturn;
431
3.06M
}
432
433
/**
434
 *  \brief Free SACK tree from a stream
435
 *
436
 *  \param stream Stream to cleanup
437
 */
438
void StreamTcpSackFreeList(TcpStream *stream)
439
561k
{
440
561k
    SCEnter();
441
442
561k
    StreamTcpSackRecord *rec = NULL, *safe = NULL;
443
561k
    RB_FOREACH_SAFE(rec, TCPSACK, &stream->sack_tree, safe) {
444
1.53k
        stream->sack_size -= (rec->re - rec->le);
445
1.53k
        TCPSACK_RB_REMOVE(&stream->sack_tree, rec);
446
1.53k
        StreamTcpSackRecordFree(rec);
447
1.53k
    }
448
449
561k
    SCReturn;
450
561k
}
451
452
453
#ifdef UNITTESTS
454
455
/**
456
 *  \test   Test the insertion of SACK ranges.
457
 *
458
 *  \retval On success it returns 1 and on failure 0.
459
 */
460
461
static int StreamTcpSackTest01 (void)
462
{
463
    TcpStream stream;
464
    memset(&stream, 0, sizeof(stream));
465
    stream.window = 100;
466
467
    StreamTcpSackInsertRange(&stream, 1, 10);
468
    FAIL_IF_NOT(stream.sack_size == 9);
469
    StreamTcpSackInsertRange(&stream, 10, 20);
470
    FAIL_IF_NOT(stream.sack_size == 19);
471
    StreamTcpSackInsertRange(&stream, 10, 20);
472
    FAIL_IF_NOT(stream.sack_size == 19);
473
    StreamTcpSackInsertRange(&stream, 1, 20);
474
    FAIL_IF_NOT(stream.sack_size == 19);
475
#ifdef DEBUG
476
    StreamTcpSackPrintList(&stream);
477
#endif /* DEBUG */
478
479
    StreamTcpSackRecord *rec = RB_MIN(TCPSACK, &stream.sack_tree);
480
    FAIL_IF_NULL(rec);
481
482
    FAIL_IF(rec->le != 1);
483
    FAIL_IF(rec->re != 20);
484
485
    FAIL_IF(StreamTcpSackedSize(&stream) != 19);
486
    StreamTcpSackFreeList(&stream);
487
    PASS;
488
}
489
490
/**
491
 *  \test   Test the insertion of SACK ranges.
492
 *
493
 *  \retval On success it returns 1 and on failure 0.
494
 */
495
496
static int StreamTcpSackTest02 (void)
497
{
498
    TcpStream stream;
499
    memset(&stream, 0, sizeof(stream));
500
    stream.window = 100;
501
502
    StreamTcpSackInsertRange(&stream, 10, 20);
503
    StreamTcpSackInsertRange(&stream, 1, 20);
504
#ifdef DEBUG
505
    StreamTcpSackPrintList(&stream);
506
#endif /* DEBUG */
507
508
    StreamTcpSackRecord *rec = RB_MIN(TCPSACK, &stream.sack_tree);
509
    FAIL_IF_NULL(rec);
510
511
    FAIL_IF(rec->le != 1);
512
    FAIL_IF(rec->re != 20);
513
514
    FAIL_IF(StreamTcpSackedSize(&stream) != 19);
515
    StreamTcpSackFreeList(&stream);
516
    PASS;
517
}
518
519
/**
520
 *  \test   Test the insertion of SACK ranges.
521
 *
522
 *  \retval On success it returns 1 and on failure 0.
523
 */
524
525
static int StreamTcpSackTest03 (void)
526
{
527
    TcpStream stream;
528
    memset(&stream, 0, sizeof(stream));
529
    stream.window = 100;
530
531
    StreamTcpSackInsertRange(&stream, 10, 20);
532
    StreamTcpSackInsertRange(&stream,  5, 15);
533
#ifdef DEBUG
534
    StreamTcpSackPrintList(&stream);
535
#endif /* DEBUG */
536
    StreamTcpSackInsertRange(&stream, 15, 25);
537
#ifdef DEBUG
538
    StreamTcpSackPrintList(&stream);
539
#endif /* DEBUG */
540
541
    StreamTcpSackRecord *rec = RB_MIN(TCPSACK, &stream.sack_tree);
542
    FAIL_IF_NULL(rec);
543
544
    FAIL_IF(rec->le != 5);
545
    FAIL_IF(rec->re != 25);
546
547
    FAIL_IF(StreamTcpSackedSize(&stream) != 20);
548
    StreamTcpSackFreeList(&stream);
549
    PASS;
550
}
551
552
/**
553
 *  \test   Test the insertion of SACK ranges.
554
 *
555
 *  \retval On success it returns 1 and on failure 0.
556
 */
557
558
static int StreamTcpSackTest04 (void)
559
{
560
    TcpStream stream;
561
    memset(&stream, 0, sizeof(stream));
562
    stream.window = 100;
563
564
    StreamTcpSackInsertRange(&stream, 0,  20);
565
    StreamTcpSackInsertRange(&stream, 30, 50);
566
    StreamTcpSackInsertRange(&stream, 10, 25);
567
#ifdef DEBUG
568
    StreamTcpSackPrintList(&stream);
569
#endif /* DEBUG */
570
571
    StreamTcpSackRecord *rec = RB_MIN(TCPSACK, &stream.sack_tree);
572
    FAIL_IF_NULL(rec);
573
574
    FAIL_IF(rec->le != 0);
575
    FAIL_IF(rec->re != 25);
576
577
    FAIL_IF(StreamTcpSackedSize(&stream) != 45);
578
    StreamTcpSackFreeList(&stream);
579
    PASS;
580
}
581
582
/**
583
 *  \test   Test the insertion of SACK ranges.
584
 *
585
 *  \retval On success it returns 1 and on failure 0.
586
 */
587
588
static int StreamTcpSackTest05 (void)
589
{
590
    TcpStream stream;
591
    memset(&stream, 0, sizeof(stream));
592
    stream.window = 100;
593
594
    StreamTcpSackInsertRange(&stream, 0,  20);
595
    StreamTcpSackInsertRange(&stream, 30, 50);
596
    StreamTcpSackInsertRange(&stream, 10, 35);
597
#ifdef DEBUG
598
    StreamTcpSackPrintList(&stream);
599
#endif /* DEBUG */
600
601
    StreamTcpSackRecord *rec = RB_MIN(TCPSACK, &stream.sack_tree);
602
    FAIL_IF_NULL(rec);
603
604
    FAIL_IF(rec->le != 0);
605
    FAIL_IF(rec->re != 50);
606
607
    FAIL_IF(StreamTcpSackedSize(&stream) != 50);
608
    StreamTcpSackFreeList(&stream);
609
    PASS;
610
}
611
612
/**
613
 *  \test   Test the insertion of SACK ranges.
614
 *
615
 *  \retval On success it returns 1 and on failure 0.
616
 */
617
618
static int StreamTcpSackTest06 (void)
619
{
620
    TcpStream stream;
621
    memset(&stream, 0, sizeof(stream));
622
    stream.window = 100;
623
624
    StreamTcpSackInsertRange(&stream, 0,  9);
625
    StreamTcpSackInsertRange(&stream, 11, 19);
626
    StreamTcpSackInsertRange(&stream, 21, 29);
627
    StreamTcpSackInsertRange(&stream, 31, 39);
628
    StreamTcpSackInsertRange(&stream, 0, 40);
629
#ifdef DEBUG
630
    StreamTcpSackPrintList(&stream);
631
#endif /* DEBUG */
632
633
    StreamTcpSackRecord *rec = RB_MIN(TCPSACK, &stream.sack_tree);
634
    FAIL_IF_NULL(rec);
635
636
    FAIL_IF(rec->le != 0);
637
    FAIL_IF(rec->re != 40);
638
639
    FAIL_IF(StreamTcpSackedSize(&stream) != 40);
640
    StreamTcpSackFreeList(&stream);
641
    PASS;
642
}
643
644
/**
645
 *  \test   Test the pruning of SACK ranges.
646
 *
647
 *  \retval On success it returns 1 and on failure 0.
648
 */
649
650
static int StreamTcpSackTest07 (void)
651
{
652
    TcpStream stream;
653
    memset(&stream, 0, sizeof(stream));
654
    stream.window = 100;
655
656
    StreamTcpSackInsertRange(&stream, 0,  9);
657
    StreamTcpSackInsertRange(&stream, 11, 19);
658
    StreamTcpSackInsertRange(&stream, 21, 29);
659
    StreamTcpSackInsertRange(&stream, 31, 39);
660
    StreamTcpSackInsertRange(&stream, 0, 40);
661
#ifdef DEBUG
662
    StreamTcpSackPrintList(&stream);
663
#endif /* DEBUG */
664
665
    StreamTcpSackRecord *rec = RB_MIN(TCPSACK, &stream.sack_tree);
666
    FAIL_IF_NULL(rec);
667
    FAIL_IF(rec->le != 0);
668
    FAIL_IF(rec->re != 40);
669
    FAIL_IF(StreamTcpSackedSize(&stream) != 40);
670
671
    stream.last_ack = 10;
672
    StreamTcpSackPruneList(&stream);
673
    FAIL_IF(StreamTcpSackedSize(&stream) != 30);
674
675
    StreamTcpSackFreeList(&stream);
676
    PASS;
677
}
678
679
/**
680
 *  \test   Test the pruning of SACK ranges.
681
 *
682
 *  \retval On success it returns 1 and on failure 0.
683
 */
684
685
static int StreamTcpSackTest08 (void)
686
{
687
    TcpStream stream;
688
    memset(&stream, 0, sizeof(stream));
689
    stream.window = 100;
690
691
    StreamTcpSackInsertRange(&stream, 0,  9);
692
    StreamTcpSackInsertRange(&stream, 11, 19);
693
    StreamTcpSackInsertRange(&stream, 21, 29);
694
    StreamTcpSackInsertRange(&stream, 31, 39);
695
    StreamTcpSackInsertRange(&stream, 0, 40);
696
#ifdef DEBUG
697
    StreamTcpSackPrintList(&stream);
698
#endif /* DEBUG */
699
700
    StreamTcpSackRecord *rec = RB_MIN(TCPSACK, &stream.sack_tree);
701
    FAIL_IF_NULL(rec);
702
    FAIL_IF(rec->le != 0);
703
    FAIL_IF(rec->re != 40);
704
    FAIL_IF(StreamTcpSackedSize(&stream) != 40);
705
706
    stream.last_ack = 41;
707
    StreamTcpSackPruneList(&stream);
708
    FAIL_IF(StreamTcpSackedSize(&stream) != 0);
709
710
    StreamTcpSackFreeList(&stream);
711
    PASS;
712
}
713
714
/**
715
 *  \test   Test the pruning of SACK ranges.
716
 *
717
 *  \retval On success it returns 1 and on failure 0.
718
 */
719
720
static int StreamTcpSackTest09 (void)
721
{
722
    TcpStream stream;
723
    memset(&stream, 0, sizeof(stream));
724
    stream.window = 100;
725
726
    StreamTcpSackInsertRange(&stream, 0,  9);
727
    StreamTcpSackInsertRange(&stream, 11, 19);
728
    StreamTcpSackInsertRange(&stream, 21, 29);
729
    StreamTcpSackInsertRange(&stream, 31, 39);
730
    StreamTcpSackInsertRange(&stream, 0, 40);
731
732
#ifdef DEBUG
733
    StreamTcpSackPrintList(&stream);
734
#endif /* DEBUG */
735
736
    StreamTcpSackRecord *rec = RB_MIN(TCPSACK, &stream.sack_tree);
737
    FAIL_IF_NULL(rec);
738
    FAIL_IF(rec->le != 0);
739
    FAIL_IF(rec->re != 40);
740
    FAIL_IF(StreamTcpSackedSize(&stream) != 40);
741
742
    stream.last_ack = 39;
743
    StreamTcpSackPruneList(&stream);
744
    FAIL_IF(StreamTcpSackedSize(&stream) != 1);
745
746
    StreamTcpSackFreeList(&stream);
747
    PASS;
748
}
749
750
/**
751
 *  \test   Test the pruning of SACK ranges.
752
 *
753
 *  \retval On success it returns 1 and on failure 0.
754
 */
755
756
static int StreamTcpSackTest10 (void)
757
{
758
    TcpStream stream;
759
    memset(&stream, 0, sizeof(stream));
760
    stream.window = 1000;
761
762
    StreamTcpSackInsertRange(&stream, 100, 119);
763
    StreamTcpSackInsertRange(&stream, 111, 119);
764
    StreamTcpSackInsertRange(&stream, 121, 129);
765
    StreamTcpSackInsertRange(&stream, 131, 139);
766
    StreamTcpSackInsertRange(&stream, 100, 140);
767
#ifdef DEBUG
768
    StreamTcpSackPrintList(&stream);
769
#endif /* DEBUG */
770
771
    StreamTcpSackRecord *rec = RB_MIN(TCPSACK, &stream.sack_tree);
772
    FAIL_IF_NULL(rec);
773
    FAIL_IF(rec->le != 100);
774
    FAIL_IF(rec->re != 140);
775
    FAIL_IF(StreamTcpSackedSize(&stream) != 40);
776
777
    stream.last_ack = 99;
778
    StreamTcpSackPruneList(&stream);
779
    FAIL_IF(StreamTcpSackedSize(&stream) != 40);
780
781
    StreamTcpSackFreeList(&stream);
782
    PASS;
783
}
784
785
/**
786
 *  \test   Test the pruning of SACK ranges.
787
 *
788
 *  \retval On success it returns 1 and on failure 0.
789
 */
790
791
static int StreamTcpSackTest11 (void)
792
{
793
    TcpStream stream;
794
    memset(&stream, 0, sizeof(stream));
795
    stream.window = 1000;
796
797
    StreamTcpSackInsertRange(&stream, 100, 119);
798
    StreamTcpSackInsertRange(&stream, 111, 119);
799
    StreamTcpSackInsertRange(&stream, 121, 129);
800
    StreamTcpSackInsertRange(&stream, 131, 139);
801
    StreamTcpSackInsertRange(&stream, 101, 140);
802
#ifdef DEBUG
803
    StreamTcpSackPrintList(&stream);
804
#endif /* DEBUG */
805
806
    StreamTcpSackRecord *rec = RB_MIN(TCPSACK, &stream.sack_tree);
807
    FAIL_IF_NULL(rec);
808
    FAIL_IF(rec->le != 100);
809
    FAIL_IF(rec->re != 140);
810
    FAIL_IF(StreamTcpSackedSize(&stream) != 40);
811
812
    stream.last_ack = 99;
813
    StreamTcpSackPruneList(&stream);
814
    FAIL_IF(StreamTcpSackedSize(&stream) != 40);
815
816
    StreamTcpSackFreeList(&stream);
817
    PASS;
818
}
819
820
/**
821
 *  \test   Test the pruning of SACK ranges.
822
 *
823
 *  \retval On success it returns 1 and on failure 0.
824
 */
825
826
static int StreamTcpSackTest12 (void)
827
{
828
    TcpStream stream;
829
    memset(&stream, 0, sizeof(stream));
830
    stream.window = 2000;
831
832
    StreamTcpSackInsertRange(&stream, 800, 1000);
833
    StreamTcpSackInsertRange(&stream, 700, 900);
834
    StreamTcpSackInsertRange(&stream, 600, 800);
835
    StreamTcpSackInsertRange(&stream, 500, 700);
836
    StreamTcpSackInsertRange(&stream, 100, 600);
837
#ifdef DEBUG
838
    StreamTcpSackPrintList(&stream);
839
#endif /* DEBUG */
840
841
    StreamTcpSackRecord *rec = RB_MIN(TCPSACK, &stream.sack_tree);
842
    FAIL_IF_NULL(rec);
843
    FAIL_IF(rec->le != 100);
844
    FAIL_IF(rec->re != 1000);
845
    FAIL_IF(StreamTcpSackedSize(&stream) != 900);
846
847
    StreamTcpSackInsertRange(&stream, 0, 1000);
848
    FAIL_IF(StreamTcpSackedSize(&stream) != 1000);
849
850
    stream.last_ack = 500;
851
    StreamTcpSackPruneList(&stream);
852
    FAIL_IF(StreamTcpSackedSize(&stream) != 500);
853
854
    StreamTcpSackFreeList(&stream);
855
    PASS;
856
}
857
858
/**
859
 *  \test   Test the insertion on out of window condition.
860
 *
861
 *  \retval On success it returns 1 and on failure 0.
862
 */
863
864
static int StreamTcpSackTest13 (void) {
865
    TcpStream stream;
866
    memset(&stream, 0, sizeof(stream));
867
    stream.last_ack = 10000;
868
    stream.window = 2000;
869
870
    for (int i = 0; i < 10; i++) {
871
        StreamTcpSackInsertRange(&stream, 100+(20*i), 110+(20*i));
872
    }
873
#ifdef DEBUG
874
    StreamTcpSackPrintList(&stream);
875
#endif /* DEBUG */
876
877
    FAIL_IF(StreamTcpSackedSize(&stream) != 0);
878
879
    StreamTcpSackFreeList(&stream);
880
    PASS;
881
}
882
883
/**
884
 *  \test   Test the insertion of out of window condition.
885
 *
886
 *  \retval On success it returns 1 and on failure 0.
887
 */
888
889
static int StreamTcpSackTest14 (void) {
890
    TcpStream stream;
891
    memset(&stream, 0, sizeof(stream));
892
    stream.last_ack = 1000;
893
    stream.window = 2000;
894
895
    for (int i = 0; i < 10; i++) {
896
        StreamTcpSackInsertRange(&stream, 4000+(20*i), 4010+(20*i));
897
    }
898
#ifdef DEBUG
899
    StreamTcpSackPrintList(&stream);
900
#endif /* DEBUG */
901
902
    FAIL_IF(StreamTcpSackedSize(&stream) != 0);
903
904
    StreamTcpSackFreeList(&stream);
905
    PASS;
906
}
907
908
#endif /* UNITTESTS */
909
910
void StreamTcpSackRegisterTests (void)
911
0
{
912
#ifdef UNITTESTS
913
    UtRegisterTest("StreamTcpSackTest01 -- Insertion", StreamTcpSackTest01);
914
    UtRegisterTest("StreamTcpSackTest02 -- Insertion", StreamTcpSackTest02);
915
    UtRegisterTest("StreamTcpSackTest03 -- Insertion", StreamTcpSackTest03);
916
    UtRegisterTest("StreamTcpSackTest04 -- Insertion", StreamTcpSackTest04);
917
    UtRegisterTest("StreamTcpSackTest05 -- Insertion", StreamTcpSackTest05);
918
    UtRegisterTest("StreamTcpSackTest06 -- Insertion", StreamTcpSackTest06);
919
    UtRegisterTest("StreamTcpSackTest07 -- Pruning", StreamTcpSackTest07);
920
    UtRegisterTest("StreamTcpSackTest08 -- Pruning", StreamTcpSackTest08);
921
    UtRegisterTest("StreamTcpSackTest09 -- Pruning", StreamTcpSackTest09);
922
    UtRegisterTest("StreamTcpSackTest10 -- Pruning", StreamTcpSackTest10);
923
    UtRegisterTest("StreamTcpSackTest11 -- Insertion && Pruning",
924
                   StreamTcpSackTest11);
925
    UtRegisterTest("StreamTcpSackTest12 -- Insertion && Pruning",
926
                   StreamTcpSackTest12);
927
    UtRegisterTest("StreamTcpSackTest13 -- Insertion out of window",
928
                   StreamTcpSackTest13);
929
    UtRegisterTest("StreamTcpSackTest14 -- Insertion out of window",
930
                   StreamTcpSackTest14);
931
#endif
932
0
}