• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OISF / suricata / 22618661228

02 Mar 2026 09:33PM UTC coverage: 42.258% (-34.4%) from 76.611%
22618661228

push

github

victorjulien
github-actions: bump actions/download-artifact from 7.0.0 to 8.0.0

Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7.0.0 to 8.0.0.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/37930b1c2...70fc10c6e)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: 8.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

91511 of 216553 relevant lines covered (42.26%)

3416852.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.72
/rust/src/filetracker.rs
1
/* Copyright (C) 2017 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
//! Gap handling and Chunk-based file transfer tracker module.
19
//!
20
//! GAP handling. If a data gap is encountered, the file is truncated
21
//! and new data is no longer pushed down to the lower level APIs.
22
//! The tracker does continue to follow the file
23
//
24
//! Tracks chunk based file transfers. Chunks may be transferred out
25
//! of order, but cannot be transferred in parallel. So only one
26
//! chunk at a time.
27
//!
28
//! Author: Victor Julien <victor@inliniac.net>
29

30
use crate::core::*;
31
use std::collections::HashMap;
32
use std::collections::hash_map::Entry::{Occupied, Vacant};
33
use crate::filecontainer::*;
34

35
#[derive(Debug)]
36
struct FileChunk {
37
    contains_gap: bool,
38
    chunk: Vec<u8>,
39
}
40

41
impl FileChunk {
42
    pub fn new(size: u32) -> FileChunk {
11✔
43
        FileChunk {
11✔
44
            contains_gap: false,
11✔
45
            chunk: Vec::with_capacity(size as usize),
11✔
46
        }
11✔
47
    }
11✔
48
}
49

50
#[derive(Debug)]
51
#[derive(Default)]
52
pub struct FileTransferTracker {
53
    pub tracked: u64,
54
    cur_ooo: u64,   // how many bytes do we have queued from ooo chunks
55
    track_id: u32,
56
    chunk_left: u32,
57

58
    pub file: FileContainer,
59
    pub file_flags: u16,
60

61
    pub tx_id: u64,
62

63
    fill_bytes: u8,
64
    pub file_open: bool,
65
    file_closed: bool,
66
    chunk_is_last: bool,
67
    chunk_is_ooo: bool,
68
    file_is_truncated: bool,
69

70
    chunks: HashMap<u64, FileChunk>,
71
    cur_ooo_chunk_offset: u64,
72

73
    in_flight: u64,
74
}
75

76
impl FileTransferTracker {
77
    pub fn new() -> FileTransferTracker {
616✔
78
        FileTransferTracker {
616✔
79
            chunks:HashMap::new(),
616✔
80
            ..Default::default()
616✔
81
        }
616✔
82
    }
616✔
83

84
    pub fn is_done(&self) -> bool {
272✔
85
        !self.file_open
272✔
86
    }
272✔
87

88
    pub fn is_initialized(&self) -> bool {
221✔
89
        return self.file_open || self.file_is_truncated || self.file_closed;
221✔
90
    }
221✔
91

92
    fn open(&mut self, config: &'static SuricataFileContext, name: &[u8]) -> i32
382✔
93
    {
382✔
94
        let r = self.file.file_open(config, self.track_id, name, self.file_flags);
382✔
95
        if r == 0 {
382✔
96
            self.file_open = true;
382✔
97
        }
382✔
98
        r
382✔
99
    }
382✔
100

101
    pub fn close(&mut self, config: &'static SuricataFileContext)
375✔
102
    {
375✔
103
        if !self.file_is_truncated {
375✔
104
            SCLogDebug!("closing file with id {}", self.track_id);
375✔
105
            self.file.file_close(config, &self.track_id, self.file_flags);
375✔
106
        }
375✔
107
        self.file_open = false;
375✔
108
        self.file_closed = true;
375✔
109
        self.tracked = 0;
375✔
110
    }
375✔
111

112
    pub fn trunc (&mut self, config: &'static SuricataFileContext)
2✔
113
    {
2✔
114
        if self.file_is_truncated || !self.file_open {
2✔
115
            return;
×
116
        }
2✔
117
        let myflags = self.file_flags | 1; // TODO util-file.c::FILE_TRUNCATED
2✔
118
        self.file.file_close(config, &self.track_id, myflags);
2✔
119
        SCLogDebug!("truncated file");
2✔
120
        self.file_is_truncated = true;
2✔
121
        self.chunks.clear();
2✔
122
        self.in_flight = 0;
2✔
123
        self.cur_ooo = 0;
2✔
124
    }
2✔
125

126
    pub fn new_chunk(&mut self, config: &'static SuricataFileContext,
816✔
127
            name: &[u8], data: &[u8], chunk_offset: u64, chunk_size: u32,
816✔
128
            fill_bytes: u8, is_last: bool, xid: &u32) -> u32
816✔
129
    {
816✔
130
        if self.chunk_left != 0 || self.fill_bytes != 0 {
816✔
131
            SCLogDebug!("current chunk incomplete: truncating");
2✔
132
            self.trunc(config);
2✔
133
        }
814✔
134

135
        SCLogDebug!("NEW CHUNK: chunk_size {} fill_bytes {}", chunk_size, fill_bytes);
136

137
        // for now assume that is_last means its really the last chunk
138
        // so no out of order chunks coming after. This means that if
139
        // the last chunk is out or order, we've missed chunks before.
140
        if chunk_offset != self.tracked {
816✔
141
            SCLogDebug!("NEW CHUNK IS OOO: expected {}, got {}", self.tracked, chunk_offset);
142
            if is_last {
19✔
143
                SCLogDebug!("last chunk is out of order, this means we missed data before");
×
144
                self.trunc(config);
×
145
            }
19✔
146
            self.chunk_is_ooo = true;
19✔
147
            self.cur_ooo_chunk_offset = chunk_offset;
19✔
148
        }
797✔
149

150
        self.chunk_left = chunk_size;
816✔
151
        self.fill_bytes = fill_bytes;
816✔
152
        self.chunk_is_last = is_last;
816✔
153

816✔
154
        if self.file_is_truncated || self.file_closed {
816✔
155
            return 0;
2✔
156
        }
814✔
157
        if !self.file_open {
814✔
158
            SCLogDebug!("NEW CHUNK: FILE OPEN");
382✔
159
            self.track_id = *xid;
382✔
160
            self.open(config, name);
382✔
161
        }
432✔
162

163
        if self.file_open {
814✔
164
            let res = self.update(config, data, 0);
814✔
165
            SCLogDebug!("NEW CHUNK: update res {:?}", res);
814✔
166
            return res;
814✔
167
        }
×
168

×
169
        0
×
170
    }
816✔
171

172
    /// update the file tracker
173
    /// If gap_size > 0 'data' should not be used.
174
    /// return how much we consumed of data
175
    pub fn update(&mut self, config: &'static SuricataFileContext, data: &[u8], gap_size: u32) -> u32
2,122✔
176
    {
2,122✔
177
        if self.file_is_truncated {
2,122✔
178
            let consumed = std::cmp::min(data.len() as u32, self.chunk_left);
×
179
            self.chunk_left = self.chunk_left.saturating_sub(data.len() as u32);
×
180
            return consumed;
×
181
        }
2,122✔
182
        let mut consumed = 0_usize;
2,122✔
183
        let is_gap = gap_size > 0;
2,122✔
184
        if is_gap || gap_size > 0 {
2,122✔
185
            SCLogDebug!("is_gap {} size {} ooo? {}", is_gap, gap_size, self.chunk_is_ooo);
2✔
186
        }
2,120✔
187

188
        if self.chunk_left == 0 && self.fill_bytes == 0 {
2,122✔
189
            //SCLogDebug!("UPDATE: nothing to do");
190
            if self.chunk_is_last {
56✔
191
                SCLogDebug!("last empty chunk, closing");
54✔
192
                self.close(config);
54✔
193
                self.chunk_is_last = false;
54✔
194
            }
54✔
195
            return 0
56✔
196
        } else if self.chunk_left == 0 {
2,066✔
197
            SCLogDebug!("FILL BYTES {} from prev run", self.fill_bytes);
198
            if data.len() >= self.fill_bytes as usize {
×
199
                consumed += self.fill_bytes as usize;
×
200
                self.fill_bytes = 0;
×
201
                SCLogDebug!("CHUNK(pre) fill bytes now 0");
×
202
            } else {
×
203
                consumed += data.len();
×
204
                self.fill_bytes -= data.len() as u8;
×
205
                SCLogDebug!("CHUNK(pre) fill bytes now still {}", self.fill_bytes);
×
206
            }
×
207
            SCLogDebug!("FILL BYTES: returning {}", consumed);
208
            return consumed as u32
×
209
        }
2,066✔
210
        SCLogDebug!("UPDATE: data {} chunk_left {}", data.len(), self.chunk_left);
2,066✔
211

2,066✔
212
        if self.chunk_left > 0 {
2,066✔
213
            if self.chunk_left <= data.len() as u32 {
2,066✔
214
                let d = &data[0..self.chunk_left as usize];
758✔
215

758✔
216
                if !self.chunk_is_ooo {
758✔
217
                    let res = self.file.file_append(config, &self.track_id, d, is_gap);
741✔
218
                    match res {
741✔
219
                        0   => { },
740✔
220
                        -2  => {
×
221
                            self.file_is_truncated = true;
×
222
                        },
×
223
                        _ => {
1✔
224
                            SCLogDebug!("got error so truncating file");
1✔
225
                            self.file_is_truncated = true;
1✔
226
                        },
1✔
227
                    }
228

229
                    self.tracked += self.chunk_left as u64;
741✔
230
                } else {
231
                    SCLogDebug!("UPDATE: appending data {} to ooo chunk at offset {}/{}",
232
                            d.len(), self.cur_ooo_chunk_offset, self.tracked);
233
                    let c = match self.chunks.entry(self.cur_ooo_chunk_offset) {
17✔
234
                        Vacant(entry) => {
7✔
235
                            entry.insert(FileChunk::new(self.chunk_left))
7✔
236
                        },
237
                        Occupied(entry) => entry.into_mut(),
10✔
238
                    };
239
                    self.cur_ooo += d.len() as u64;
17✔
240
                    c.contains_gap |= is_gap;
17✔
241
                    c.chunk.extend(d);
17✔
242

17✔
243
                    self.in_flight += d.len() as u64;
17✔
244
                    SCLogDebug!("{:p} in_flight {}", self, self.in_flight);
245
                }
246

247
                consumed += self.chunk_left as usize;
758✔
248
                if self.fill_bytes > 0 {
758✔
249
                    let extra = data.len() - self.chunk_left as usize;
8✔
250
                    if extra >= self.fill_bytes as usize {
8✔
251
                        consumed += self.fill_bytes as usize;
×
252
                        self.fill_bytes = 0;
×
253
                        SCLogDebug!("CHUNK(post) fill bytes now 0");
×
254
                    } else {
8✔
255
                        consumed += extra;
8✔
256
                        self.fill_bytes -= extra as u8;
8✔
257
                        SCLogDebug!("CHUNK(post) fill bytes now still {}", self.fill_bytes);
8✔
258
                    }
8✔
259
                    self.chunk_left = 0;
8✔
260
                } else {
261
                    self.chunk_left = 0;
750✔
262

750✔
263
                    if !self.chunk_is_ooo {
750✔
264
                        loop {
265
                            let _offset = self.tracked;
737✔
266
                            match self.chunks.remove(&self.tracked) {
737✔
267
                                Some(c) => {
4✔
268
                                    self.in_flight -= c.chunk.len() as u64;
4✔
269

4✔
270
                                    let res = self.file.file_append(config, &self.track_id, &c.chunk, c.contains_gap);
4✔
271
                                    match res {
4✔
272
                                        0   => { },
4✔
273
                                        -2  => {
×
274
                                            self.file_is_truncated = true;
×
275
                                        },
×
276
                                        _ => {
×
277
                                            SCLogDebug!("got error so truncating file");
×
278
                                            self.file_is_truncated = true;
×
279
                                        },
×
280
                                    }
281

282
                                    self.tracked += c.chunk.len() as u64;
4✔
283
                                    self.cur_ooo -= c.chunk.len() as u64;
4✔
284

285
                                    SCLogDebug!("STORED OOO CHUNK at offset {}, tracked now {}, stored len {}", _offset, self.tracked, c.chunk.len());
286
                                },
287
                                _ => {
288
                                    SCLogDebug!("NO STORED CHUNK found at _offset {}", self.tracked);
289
                                    break;
733✔
290
                                },
291
                            };
292
                        }
293
                    } else {
17✔
294
                        SCLogDebug!("UPDATE: complete ooo chunk. Offset {}", self.cur_ooo_chunk_offset);
17✔
295

17✔
296
                        self.chunk_is_ooo = false;
17✔
297
                        self.cur_ooo_chunk_offset = 0;
17✔
298
                    }
17✔
299
                }
300
                if self.chunk_is_last {
758✔
301
                    SCLogDebug!("last chunk, closing");
56✔
302
                    self.close(config);
56✔
303
                    self.chunk_is_last = false;
56✔
304
                } else {
702✔
305
                    SCLogDebug!("NOT last chunk, keep going");
702✔
306
                }
702✔
307

308
            } else {
309
                if !self.chunk_is_ooo {
1,308✔
310
                    let res = self.file.file_append(config, &self.track_id, data, is_gap);
1,298✔
311
                    match res {
1,298✔
312
                        0   => { },
1,298✔
313
                        -2  => {
×
314
                            self.file_is_truncated = true;
×
315
                        },
×
316
                        _ => {
×
317
                            SCLogDebug!("got error so truncating file");
×
318
                            self.file_is_truncated = true;
×
319
                        },
×
320
                    }
321
                    self.tracked += data.len() as u64;
1,298✔
322
                } else {
323
                    let c = match self.chunks.entry(self.cur_ooo_chunk_offset) {
10✔
324
                        Vacant(entry) => entry.insert(FileChunk::new(32768)),
4✔
325
                        Occupied(entry) => entry.into_mut(),
6✔
326
                    };
327
                    c.chunk.extend(data);
10✔
328
                    c.contains_gap |= is_gap;
10✔
329
                    self.cur_ooo += data.len() as u64;
10✔
330
                    self.in_flight += data.len() as u64;
10✔
331
                }
332

333
                self.chunk_left -= data.len() as u32;
1,308✔
334
                consumed += data.len();
1,308✔
335
            }
336
        }
×
337
        consumed as u32
2,066✔
338
    }
2,122✔
339

340
    pub fn get_queued_size(&self) -> u64 {
×
341
        self.cur_ooo
×
342
    }
×
343

344
    pub fn get_inflight_size(&self) -> u64 {
296✔
345
        self.in_flight
296✔
346
    }
296✔
347
    pub fn get_inflight_cnt(&self) -> usize {
296✔
348
        self.chunks.len()
296✔
349
    }
296✔
350
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc