]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
app/frames: initial support
authorVictor Julien <vjulien@oisf.net>
Fri, 3 Dec 2021 06:40:56 +0000 (07:40 +0100)
committerVictor Julien <vjulien@oisf.net>
Mon, 17 Jan 2022 20:44:04 +0000 (21:44 +0100)
The idea of stream frames is that the applayer parsers can tag PDUs and
other arbitrary frames in the stream while parsing. These frames can then
be inspected from the rule language. This will allow rules that are more
precise and less costly.

The frames are stored per direction in the `AppLayerParserState` and will only
be initialized when actual frames are in use. The per direction storage has a
fixed size static portion and dynamic support for a larger number. This is done
for effeciency.

When the Stream Buffer slides, frames are updated as they use offsets relative
to the stream. A negative offset is used for frames that started before the
current window.

Frames have events to inspect/log parser errors that don't fit the TX model.

Frame id starts at 1. So implementations can keep track of frame ids where 0
is not set.

Frames affect TCP window sliding. The frames keep a "left edge" which
signifies how much data to keep for frames that are still in progress.

src/Makefile.am
src/app-layer-frames.c [new file with mode: 0644]
src/app-layer-frames.h [new file with mode: 0644]
src/app-layer-parser.c
src/app-layer-parser.h
src/app-layer.c
src/flow-worker.c
src/stream-tcp-list.c
src/stream-tcp-reassemble.c

index f7edaaa518a65ab1723855141f72ab717d7987a5..61fdfae5e89ce385c06b1b3ccefb127dd047290d 100755 (executable)
@@ -22,6 +22,7 @@ noinst_HEADERS = \
        app-layer-enip.h \
        app-layer-events.h \
        app-layer-expectation.h \
+       app-layer-frames.h \
        app-layer-ftp.h \
        app-layer.h \
        app-layer-htp-body.h \
@@ -611,6 +612,7 @@ libsuricata_c_a_SOURCES = \
        app-layer-events.c \
        app-layer-expectation.c \
        app-layer-ftp.c \
+       app-layer-frames.c \
        app-layer-htp-body.c \
        app-layer-htp.c \
        app-layer-htp-file.c \
diff --git a/src/app-layer-frames.c b/src/app-layer-frames.c
new file mode 100644 (file)
index 0000000..87c0fda
--- /dev/null
@@ -0,0 +1,781 @@
+/* Copyright (C) 2007-2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have frameeived a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Victor Julien <victor@inliniac.net>
+ *
+ */
+
+#include "suricata-common.h"
+#include "debug.h"
+#include "util-print.h"
+
+#include "stream-tcp.h"
+#include "app-layer-frames.h"
+
+static void FrameDebug(const char *prefix, const Frames *frames, const Frame *frame)
+{
+#ifdef DEBUG
+    const char *type_name =
+            frames ? AppLayerParserGetFrameNameById(frames->ipproto, frames->alproto, frame->type)
+                   : "<unknown>";
+    SCLogDebug("[%s] %p: frame: %p type %u/%s id %" PRIi64 " flags %02x rel_offset:%" PRIi64
+               ", len:%" PRIi64 ", events:%u %u/%u/%u/%u",
+            prefix, frames, frame, frame->type, type_name, frame->id, frame->flags,
+            frame->rel_offset, frame->len, frame->event_cnt, frame->events[0], frame->events[1],
+            frame->events[2], frame->events[3]);
+#endif
+}
+
+Frame *FrameGetById(Frames *frames, const int64_t id)
+{
+    for (uint16_t i = 0; i < frames->cnt; i++) {
+        if (i < FRAMES_STATIC_CNT) {
+            Frame *frame = &frames->sframes[i];
+            if (frame->id == id)
+                return frame;
+        } else {
+            const uint16_t o = i - FRAMES_STATIC_CNT;
+            Frame *frame = &frames->dframes[o];
+            if (frame->id == id)
+                return frame;
+        }
+    }
+    return NULL;
+}
+
+Frame *FrameGetByIndex(Frames *frames, const uint32_t idx)
+{
+    if (idx >= frames->cnt)
+        return NULL;
+
+    if (idx < FRAMES_STATIC_CNT) {
+        Frame *frame = &frames->sframes[idx];
+        FrameDebug("get_by_idx(s)", frames, frame);
+        return frame;
+    } else {
+        const uint16_t o = idx - FRAMES_STATIC_CNT;
+        Frame *frame = &frames->dframes[o];
+        FrameDebug("get_by_idx(d)", frames, frame);
+        return frame;
+    }
+}
+
+// TODO review rel_offset logic. App-layer passes STREAM_APP_PROGRESS as
+// offset, but I think we're using rel_offset relative to BASE_PROGRESS
+// here which changes only on slide.
+static Frame *FrameNew(Frames *frames, int64_t rel_offset, int64_t len)
+{
+    BUG_ON(frames == NULL);
+
+    if (frames->cnt < FRAMES_STATIC_CNT) {
+        Frame *frame = &frames->sframes[frames->cnt];
+        frames->sframes[frames->cnt].rel_offset = rel_offset;
+        frames->sframes[frames->cnt].len = len;
+        frames->sframes[frames->cnt].id = ++frames->base_id;
+        frames->cnt++;
+        return frame;
+    } else if (frames->dframes == NULL) {
+        BUG_ON(frames->dyn_size != 0);
+        BUG_ON(frames->cnt != FRAMES_STATIC_CNT);
+
+        frames->dframes = SCCalloc(8, sizeof(Frame));
+        if (frames->dframes == NULL) {
+            return NULL;
+        }
+        frames->cnt++;
+        BUG_ON(frames->cnt != FRAMES_STATIC_CNT + 1);
+
+        frames->dyn_size = 8;
+        frames->dframes[0].rel_offset = rel_offset;
+        frames->dframes[0].len = len;
+        frames->dframes[0].id = ++frames->base_id;
+        return &frames->dframes[0];
+    } else {
+        BUG_ON(frames->cnt < FRAMES_STATIC_CNT);
+
+        /* need to handle dynamic storage of frames now */
+        const uint16_t dyn_cnt = frames->cnt - FRAMES_STATIC_CNT;
+        if (dyn_cnt < frames->dyn_size) {
+            BUG_ON(frames->dframes == NULL);
+
+            // fall through
+        } else {
+            if (frames->dyn_size == 256) {
+                SCLogDebug("limit reached! 256 dynamic frames already");
+                // limit reached
+                // TODO figure out if this should lead to an event of sorts
+                return NULL;
+            }
+
+            /* realloc time */
+            uint16_t new_dyn_size = frames->dyn_size * 2;
+            uint32_t new_alloc_size = new_dyn_size * sizeof(Frame);
+
+            void *ptr = SCRealloc(frames->dframes, new_alloc_size);
+            if (ptr == NULL) {
+                return NULL;
+            }
+
+            memset((uint8_t *)ptr + (frames->dyn_size * sizeof(Frame)), 0x00,
+                    (frames->dyn_size * sizeof(Frame)));
+            frames->dframes = ptr;
+            frames->dyn_size = new_dyn_size;
+        }
+
+        frames->cnt++;
+        frames->dframes[dyn_cnt].rel_offset = rel_offset;
+        frames->dframes[dyn_cnt].len = len;
+        frames->dframes[dyn_cnt].id = ++frames->base_id;
+        return &frames->dframes[dyn_cnt];
+    }
+}
+
+static void FrameClean(Frame *frame)
+{
+    memset(frame, 0, sizeof(*frame));
+}
+
+static void FrameCopy(Frame *dst, Frame *src)
+{
+    memcpy(dst, src, sizeof(*dst));
+}
+
+static void AppLayerFrameDumpForFrames(const char *prefix, const Frames *frames)
+{
+    SCLogDebug("prefix: %s", prefix);
+    for (uint16_t i = 0; i < frames->cnt; i++) {
+        if (i < FRAMES_STATIC_CNT) {
+            const Frame *frame = &frames->sframes[i];
+            FrameDebug(prefix, frames, frame);
+        } else {
+            const uint16_t o = i - FRAMES_STATIC_CNT;
+            const Frame *frame = &frames->dframes[o];
+            FrameDebug(prefix, frames, frame);
+        }
+    }
+    SCLogDebug("prefix: %s", prefix);
+}
+
+static inline uint64_t FrameLeftEdge(
+        const TcpStream *stream, const Frame *frame, const int64_t base_offset)
+{
+    const int64_t app_progress = STREAM_APP_PROGRESS(stream);
+    BUG_ON(base_offset > app_progress);
+
+    const int64_t frame_offset = base_offset + frame->rel_offset;
+    const int64_t frame_data = app_progress - frame_offset;
+
+    SCLogDebug("base_offset %" PRIi64 ", app_progress %" PRIi64, base_offset, app_progress);
+    SCLogDebug("frame_offset %" PRIi64 ", frame_data %" PRIi64 ", frame->len %" PRIi64,
+            frame_offset, frame_data, frame->len);
+    BUG_ON(frame_offset < 0);
+    BUG_ON(frame_offset > app_progress);
+
+    /* length unknown, make sure to have at least 2500 */
+    if (frame->len < 0) {
+        if (frame_data <= 2500) {
+            SCLogDebug("got <= 2500 bytes (%" PRIu64 "), returning offset %" PRIu64, frame_data,
+                    frame_offset);
+            return frame_offset;
+        } else {
+            SCLogDebug("got > 2500 bytes (%" PRIu64 "), returning offset %" PRIu64, frame_data,
+                    (frame_offset + (frame_data - 2500)));
+            return frame_offset + (frame_data - 2500);
+        }
+
+        /* length specified */
+    } else {
+        /* have all data for the frame, we can skip it */
+        if (frame->len <= frame_data) {
+            uint64_t x = frame_offset + frame_data;
+            SCLogDebug("x %" PRIu64, x);
+            return x;
+            /*
+
+                [ stream      <frame_data> ]
+                             [ frame        .......]
+
+             */
+        } else if (frame_data < 2500) {
+            uint64_t x = frame_offset;
+            SCLogDebug("x %" PRIu64, x);
+            return x;
+        } else {
+            uint64_t x = frame_offset + (frame_data - 2500);
+            SCLogDebug("x %" PRIu64, x);
+            return x;
+        }
+    }
+}
+#if 0
+static inline uint64_t FramesLeftEdge(const TcpStream *stream, const Frames *frames)
+{
+    uint64_t le = STREAM_APP_PROGRESS(stream);
+    for (uint16_t i = 0; i < frames->cnt; i++) {
+        if (i < FRAMES_STATIC_CNT) {
+            const Frame *frame = &frames->sframes[i];
+            le = MIN(le, FrameLeftEdge(stream, frame));
+        } else {
+            const uint16_t o = i - FRAMES_STATIC_CNT;
+            const Frame *frame = &frames->dframes[o];
+            le = MIN(le, FrameLeftEdge(stream, frame));
+        }
+    }
+    return le;
+}
+#endif
+
+/** Stream buffer slides forward, we need to update and age out
+ *  frame offsets/frames. Aging out means we move existing frames
+ *  into the slots we'd free up.
+ *
+ *  Start:
+ *
+ *  [ stream ]
+ *    [ frame   ...........]
+ *      rel_offset: 2
+ *      len: 19
+ *
+ *  Slide:
+ *         [ stream ]
+ *    [ frame ....          .]
+ *      rel_offset: -10
+ *       len: 19
+ *
+ *  Slide:
+ *                [ stream ]
+ *    [ frame ...........    ]
+ *      rel_offset: -16
+ *      len: 19
+ */
+static int FrameSlide(const char *ds, Frames *frames, const TcpStream *stream, const uint32_t slide)
+{
+    SCLogDebug("start: left edge %" PRIu64 ", left_edge_rel %u, stream base %" PRIu64
+               ", next %" PRIu64,
+            (uint64_t)frames->left_edge_rel + STREAM_BASE_OFFSET(stream), frames->left_edge_rel,
+            STREAM_BASE_OFFSET(stream), STREAM_BASE_OFFSET(stream) + slide);
+    BUG_ON(frames == NULL);
+    SCLogDebug("%s frames %p: sliding %u bytes", ds, frames, slide);
+    uint64_t le = STREAM_APP_PROGRESS(stream);
+
+    if (slide >= frames->progress_rel)
+        frames->progress_rel = 0;
+    else
+        frames->progress_rel -= slide;
+
+    const uint64_t next_base = STREAM_BASE_OFFSET(stream) + slide;
+    const uint16_t start = frames->cnt;
+    uint16_t removed = 0;
+    uint16_t x = 0;
+    for (uint16_t i = 0; i < frames->cnt; i++) {
+        if (i < FRAMES_STATIC_CNT) {
+            Frame *frame = &frames->sframes[i];
+            FrameDebug("slide(s)", frames, frame);
+            if (frame->len >= 0 &&
+                    frame->rel_offset + frame->len <= (int64_t)slide) { // TODO check seems off
+                // remove by not incrementing 'x'
+                SCLogDebug("removing %p id %" PRIi64, frame, frame->id);
+                FrameClean(frame);
+                removed++;
+            } else {
+                Frame *nframe = &frames->sframes[x];
+                FrameCopy(nframe, frame);
+                nframe->rel_offset -= slide; /* turns negative if start if before window */
+                if (frame != nframe) {
+                    FrameClean(frame);
+                }
+                le = MIN(le, FrameLeftEdge(stream, nframe, next_base));
+                x++;
+            }
+        } else {
+            const uint16_t o = i - FRAMES_STATIC_CNT;
+            Frame *frame = &frames->dframes[o];
+            FrameDebug("slide(d)", frames, frame);
+            if (frame->len >= 0 && frame->rel_offset + frame->len <= (int64_t)slide) {
+                // remove by not incrementing 'x'
+                SCLogDebug("removing %p id %" PRIi64, frame, frame->id);
+                FrameClean(frame);
+                removed++;
+            } else {
+                Frame *nframe;
+                if (x >= FRAMES_STATIC_CNT) {
+                    nframe = &frames->dframes[x - FRAMES_STATIC_CNT];
+                } else {
+                    nframe = &frames->sframes[x];
+                }
+                FrameCopy(nframe, frame);
+                nframe->rel_offset -= slide; /* turns negative if start is before window */
+                if (frame != nframe) {
+                    FrameClean(frame);
+                }
+                le = MIN(le, FrameLeftEdge(stream, nframe, next_base));
+                x++;
+            }
+        }
+    }
+    frames->cnt = x;
+    uint64_t o = STREAM_BASE_OFFSET(stream) + slide;
+    frames->left_edge_rel = le - (STREAM_BASE_OFFSET(stream) + slide);
+
+#ifdef DEBUG
+    SCLogDebug("end: left edge %" PRIu64 ", left_edge_rel %u, stream base %" PRIu64
+               " (+slide), cnt %u, removed %u, start %u",
+            (uint64_t)frames->left_edge_rel + STREAM_BASE_OFFSET(stream) + slide,
+            frames->left_edge_rel, STREAM_BASE_OFFSET(stream) + slide, frames->cnt, removed, start);
+    char pf[32] = "";
+    snprintf(pf, sizeof(pf), "%s:post_slide", ds);
+    AppLayerFrameDumpForFrames(pf, frames);
+#endif
+    BUG_ON(o > le);
+    BUG_ON(x != start - removed);
+    return 0;
+}
+
+void AppLayerFramesUpdateProgress(
+        Flow *f, TcpStream *stream, const uint64_t progress, const uint8_t direction)
+{
+    FramesContainer *frames_container = AppLayerFramesGetContainer(f);
+    if (frames_container == NULL)
+        return;
+
+    Frames *frames;
+    if (direction == STREAM_TOSERVER) {
+        frames = &frames_container->toserver;
+    } else {
+        frames = &frames_container->toclient;
+    }
+
+    const uint32_t slide = progress - STREAM_APP_PROGRESS(stream);
+    frames->progress_rel += slide;
+}
+
+void AppLayerFramesSlide(Flow *f, const uint32_t slide, const uint8_t direction)
+{
+    FramesContainer *frames_container = AppLayerFramesGetContainer(f);
+    if (frames_container == NULL)
+        return;
+    Frames *frames;
+    TcpSession *ssn = f->protoctx;
+    TcpStream *stream;
+    if (direction == STREAM_TOSERVER) {
+        stream = &ssn->client;
+        frames = &frames_container->toserver;
+        FrameSlide("toserver", frames, stream, slide);
+    } else {
+        stream = &ssn->server;
+        frames = &frames_container->toclient;
+        FrameSlide("toclient", frames, stream, slide);
+    }
+}
+
+static void FrameFreeSingleFrame(Frames *frames, Frame *r)
+{
+    FrameDebug("free", frames, r);
+    FrameClean(r);
+}
+
+void FramesFree(Frames *frames)
+{
+    BUG_ON(frames == NULL);
+
+    for (uint16_t i = 0; i < frames->cnt; i++) {
+        if (i < FRAMES_STATIC_CNT) {
+            Frame *r = &frames->sframes[i];
+            FrameFreeSingleFrame(frames, r);
+        } else {
+            const uint16_t o = i - FRAMES_STATIC_CNT;
+            Frame *r = &frames->dframes[o];
+            FrameFreeSingleFrame(frames, r);
+        }
+    }
+    SCFree(frames->dframes);
+    frames->dframes = NULL;
+}
+
+/** \brief create new frame using a pointer to start of the frame
+ */
+Frame *AppLayerFrameNewByPointer(Flow *f, const StreamSlice *stream_slice,
+        const uint8_t *frame_start, const int64_t len, int dir, uint8_t frame_type)
+{
+    SCLogDebug("stream_slice offset %" PRIu64, stream_slice->offset);
+    SCLogDebug("frame_start %p stream_slice->input %p", frame_start, stream_slice->input);
+
+    /* workarounds for many (unit|fuzz)tests not handling TCP data properly */
+#if defined(UNITTESTS) || defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
+    if (f->protoctx == NULL)
+        return NULL;
+    if (frame_start < stream_slice->input ||
+            frame_start >= stream_slice->input + stream_slice->input_len)
+        return NULL;
+#endif
+    BUG_ON(frame_start < stream_slice->input);
+    BUG_ON(stream_slice->input == NULL);
+    BUG_ON(f->proto != IPPROTO_TCP);
+    BUG_ON(f->protoctx == NULL);
+
+    ptrdiff_t ptr_offset = frame_start - stream_slice->input;
+#ifdef DEBUG
+    uint64_t offset = ptr_offset + stream_slice->offset;
+    SCLogDebug("flow %p direction %s frame %p starting at %" PRIu64 " len %" PRIi64
+               " (offset %" PRIu64 ")",
+            f, dir == 0 ? "toserver" : "toclient", frame_start, offset, len, stream_slice->offset);
+#endif
+    BUG_ON(f->alparser == NULL);
+
+    FramesContainer *frames_container = AppLayerFramesSetupContainer(f);
+    if (frames_container == NULL)
+        return NULL;
+
+    TcpStream *stream;
+    TcpSession *ssn = f->protoctx;
+    Frames *frames;
+    if (dir == 0) {
+        frames = &frames_container->toserver;
+        stream = &ssn->client;
+    } else {
+        frames = &frames_container->toclient;
+        stream = &ssn->server;
+    }
+
+    int64_t abs_frame_offset = stream_slice->offset + (int64_t)ptr_offset;
+    int64_t rel_offset = abs_frame_offset - STREAM_BASE_OFFSET(stream);
+
+    Frame *r = FrameNew(frames, rel_offset, len);
+    if (r != NULL) {
+        r->type = frame_type;
+    }
+    return r;
+}
+
+/** \brief create new frame using a relative offset from the start of the stream slice
+ */
+Frame *AppLayerFrameNewByRelativeOffset(Flow *f, const StreamSlice *stream_slice,
+        const uint32_t frame_start_rel, const int64_t len, int dir, uint8_t frame_type)
+{
+    /* workarounds for many (unit|fuzz)tests not handling TCP data properly */
+#if defined(UNITTESTS) || defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
+    if (f->protoctx == NULL)
+        return NULL;
+    if (stream_slice->input == NULL)
+        return NULL;
+#endif
+    BUG_ON(stream_slice->input == NULL);
+    BUG_ON(f->proto != IPPROTO_TCP);
+    BUG_ON(f->protoctx == NULL);
+    BUG_ON(f->alparser == NULL);
+
+    FramesContainer *frames_container = AppLayerFramesSetupContainer(f);
+    if (frames_container == NULL)
+        return NULL;
+
+    TcpStream *stream;
+    TcpSession *ssn = f->protoctx;
+    Frames *frames;
+    if (dir == 0) {
+        frames = &frames_container->toserver;
+        stream = &ssn->client;
+    } else {
+        frames = &frames_container->toclient;
+        stream = &ssn->server;
+    }
+
+    const uint64_t base = STREAM_BASE_OFFSET(stream);
+#ifdef DEBUG
+    const uint64_t app = STREAM_APP_PROGRESS(stream);
+    const uint64_t app_offset = app - base;
+    const uint64_t slice_offset = stream_slice->offset - base;
+
+    SCLogDebug("app %" PRIu64 ", base %" PRIu64 ", slice %" PRIu64, app, base, slice_offset);
+    SCLogDebug("app_offset %" PRIu64 ", slice_offset %" PRIu64, app_offset, slice_offset);
+#endif
+    const uint64_t frame_abs_offset = (uint64_t)frame_start_rel + stream_slice->offset;
+    const uint64_t frame_base_offset = frame_abs_offset - base;
+
+    SCLogDebug("frame_start_rel %u frame_abs_offset %" PRIu64 ", frame_base_offset %" PRIu64,
+            frame_start_rel, frame_abs_offset, frame_base_offset);
+
+    int64_t rel_offset = frame_base_offset;
+#ifdef DEBUG
+    const char *type_name = AppLayerParserGetFrameNameById(f->proto, f->alproto, frame_type);
+    SCLogDebug("flow %p direction %s frame offset %u rel_offset %" PRIi64 " (abs %" PRIu64
+               ") starting at %" PRIu64 " len %" PRIi64 " (offset %" PRIu64 ") type %u/%s",
+            f, dir == 0 ? "toserver" : "toclient", frame_start_rel, rel_offset, frame_abs_offset,
+            frame_abs_offset, len, stream_slice->offset, frame_type, type_name);
+#endif
+
+    Frame *r = FrameNew(frames, rel_offset, len);
+    if (r != NULL) {
+        r->type = frame_type;
+    }
+    return r;
+}
+
+void AppLayerFrameDump(Flow *f)
+{
+    if (f->proto == IPPROTO_TCP && f->protoctx && f->alparser) {
+        FramesContainer *frames_container = AppLayerFramesGetContainer(f);
+        if (frames_container != NULL) {
+            AppLayerFrameDumpForFrames("toserver::dump", &frames_container->toserver);
+            AppLayerFrameDumpForFrames("toclient::dump", &frames_container->toclient);
+        }
+    }
+}
+
+/** \brief create new frame using the absolute offset from the start of the stream
+ */
+Frame *AppLayerFrameNewByAbsoluteOffset(Flow *f, const StreamSlice *stream_slice,
+        const uint64_t frame_start, const int64_t len, int dir, uint8_t frame_type)
+{
+    /* workarounds for many (unit|fuzz)tests not handling TCP data properly */
+#if defined(UNITTESTS) || defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
+    if (f->protoctx == NULL)
+        return NULL;
+    if (stream_slice->input == NULL)
+        return NULL;
+#endif
+    BUG_ON(stream_slice->input == NULL);
+    BUG_ON(f->proto != IPPROTO_TCP);
+    BUG_ON(f->protoctx == NULL);
+    BUG_ON(f->alparser == NULL);
+    BUG_ON(frame_start < stream_slice->offset);
+    BUG_ON(frame_start - stream_slice->offset >= (uint64_t)INT_MAX);
+
+    FramesContainer *frames_container = AppLayerFramesSetupContainer(f);
+    if (frames_container == NULL)
+        return NULL;
+
+    TcpSession *ssn = f->protoctx;
+    TcpStream *stream;
+    Frames *frames;
+    if (dir == 0) {
+        stream = &ssn->client;
+        frames = &frames_container->toserver;
+    } else {
+        stream = &ssn->server;
+        frames = &frames_container->toclient;
+    }
+
+    const uint64_t frame_start_rel = frame_start - STREAM_BASE_OFFSET(stream);
+#ifdef DEBUG
+    SCLogDebug("flow %p direction %s frame offset %" PRIu64 " (abs %" PRIu64
+               ") starting at %" PRIu64 " len %" PRIi64 " (offset %" PRIu64 ")",
+            f, dir == 0 ? "toserver" : "toclient", frame_start_rel, frame_start, frame_start, len,
+            stream_slice->offset);
+#endif
+    Frame *r = FrameNew(frames, (uint32_t)frame_start_rel, len);
+    if (r != NULL) {
+        r->type = frame_type;
+    }
+    return r;
+}
+
+void AppLayerFrameAddEvent(Frame *r, uint8_t e)
+{
+    if (r != NULL) {
+        if (r->event_cnt < 4) { // TODO
+            r->events[r->event_cnt++] = e;
+        }
+        FrameDebug("add_event", NULL, r);
+    }
+}
+
+void AppLayerFrameAddEventById(Flow *f, const int dir, const FrameId id, uint8_t e)
+{
+    Frame *frame = AppLayerFrameGetById(f, dir, id);
+    AppLayerFrameAddEvent(frame, e);
+}
+
+FrameId AppLayerFrameGetId(Frame *r)
+{
+    if (r != NULL) {
+        return r->id;
+    } else {
+        return -1;
+    }
+}
+
+void AppLayerFrameSetLength(Frame *frame, int64_t len)
+{
+    if (frame != NULL) {
+        frame->len = len;
+        FrameDebug("set_length", NULL, frame);
+    }
+}
+
+void AppLayerFrameSetLengthById(Flow *f, const int dir, const FrameId id, int64_t len)
+{
+    Frame *frame = AppLayerFrameGetById(f, dir, id);
+    AppLayerFrameSetLength(frame, len);
+}
+
+void AppLayerFrameSetTxId(Frame *r, uint64_t tx_id)
+{
+    if (r != NULL) {
+        r->flags |= FRAME_FLAG_TX_ID_SET;
+        r->tx_id = tx_id;
+        FrameDebug("set_txid", NULL, r);
+    }
+}
+
+void AppLayerFrameSetTxIdById(Flow *f, const int dir, const FrameId id, uint64_t tx_id)
+{
+    Frame *frame = AppLayerFrameGetById(f, dir, id);
+    AppLayerFrameSetTxId(frame, tx_id);
+}
+
+Frame *AppLayerFrameGetById(Flow *f, const int dir, const FrameId frame_id)
+{
+    FramesContainer *frames_container = AppLayerFramesGetContainer(f);
+    if (frames_container == NULL)
+        return NULL;
+
+    Frames *frames;
+    if (dir == 0) {
+        frames = &frames_container->toserver;
+    } else {
+        frames = &frames_container->toclient;
+    }
+    return FrameGetById(frames, frame_id);
+}
+
+static inline bool FrameIsDone(
+        const Frame *frame, const uint64_t abs_offset, const uint64_t abs_right_edge)
+{
+    /* frame with negative length means we don't know the size yet. */
+    if (frame->len < 0)
+        return false;
+
+    const int64_t frame_abs_offset = (int64_t)abs_offset + frame->rel_offset;
+    const int64_t frame_right_edge = frame_abs_offset + frame->len;
+    if ((uint64_t)frame_right_edge <= abs_right_edge) {
+        SCLogDebug("frame %p id %" PRIi64 " is done", frame, frame->id);
+        return true;
+    }
+    return false;
+}
+
+static void FramePrune(Frames *frames, const TcpStream *stream, const bool eof)
+{
+    const uint64_t frames_le_start = (uint64_t)frames->left_edge_rel + STREAM_BASE_OFFSET(stream);
+    SCLogDebug("start: left edge %" PRIu64 ", left_edge_rel %u, stream base %" PRIu64,
+            (uint64_t)frames->left_edge_rel + STREAM_BASE_OFFSET(stream), frames->left_edge_rel,
+            STREAM_BASE_OFFSET(stream));
+    const uint64_t abs_offset = STREAM_BASE_OFFSET(stream) + (uint64_t)frames->progress_rel;
+    const uint64_t acked = StreamTcpGetUsable(stream, eof);
+    uint64_t le = STREAM_APP_PROGRESS(stream);
+
+    const uint16_t start = frames->cnt;
+    uint16_t removed = 0;
+    uint16_t x = 0;
+    for (uint16_t i = 0; i < frames->cnt; i++) {
+        if (i < FRAMES_STATIC_CNT) {
+            Frame *frame = &frames->sframes[i];
+            FrameDebug("prune(s)", frames, frame);
+            if (eof || FrameIsDone(frame, abs_offset, acked)) {
+                // remove by not incrementing 'x'
+                SCLogDebug("removing %p id %" PRIi64, frame, frame->id);
+                FrameDebug("remove(s)", frames, frame);
+                FrameClean(frame);
+                removed++;
+            } else {
+                const uint64_t fle = FrameLeftEdge(stream, frame, STREAM_BASE_OFFSET(stream));
+                le = MIN(le, fle);
+                SCLogDebug("le %" PRIu64 ", frame fle %" PRIu64, le, fle);
+                Frame *nframe = &frames->sframes[x];
+                FrameCopy(nframe, frame);
+                if (frame != nframe) {
+                    FrameClean(frame);
+                }
+                x++;
+            }
+        } else {
+            const uint16_t o = i - FRAMES_STATIC_CNT;
+            Frame *frame = &frames->dframes[o];
+            FrameDebug("prune(d)", frames, frame);
+            if (eof || FrameIsDone(frame, abs_offset, acked)) {
+                // remove by not incrementing 'x'
+                SCLogDebug("removing %p id %" PRIi64, frame, frame->id);
+                FrameDebug("remove(d)", frames, frame);
+                FrameClean(frame);
+                removed++;
+            } else {
+                const uint64_t fle = FrameLeftEdge(stream, frame, STREAM_BASE_OFFSET(stream));
+                le = MIN(le, fle);
+                SCLogDebug("le %" PRIu64 ", frame fle %" PRIu64, le, fle);
+                Frame *nframe;
+                if (x >= FRAMES_STATIC_CNT) {
+                    nframe = &frames->dframes[x - FRAMES_STATIC_CNT];
+                } else {
+                    nframe = &frames->sframes[x];
+                }
+                FrameCopy(nframe, frame);
+                if (frame != nframe) {
+                    FrameClean(frame);
+                }
+                x++;
+            }
+        }
+    }
+    frames->cnt = x;
+    frames->left_edge_rel = le - STREAM_BASE_OFFSET(stream);
+#ifdef DEBUG
+    SCLogDebug("end: left edge %" PRIu64 ", left_edge_rel %u, stream base %" PRIu64
+               ", cnt %u, removed %u, start %u",
+            (uint64_t)frames->left_edge_rel + STREAM_BASE_OFFSET(stream), frames->left_edge_rel,
+            STREAM_BASE_OFFSET(stream), frames->cnt, removed, start);
+    AppLayerFrameDumpForFrames("post_slide", frames);
+#endif
+    BUG_ON(le < STREAM_BASE_OFFSET(stream));
+    if (frames->cnt > 0) { // if we removed all this can fail
+        BUG_ON(frames_le_start > le);
+    }
+    BUG_ON(x != start - removed);
+}
+
+void FramesPrune(Flow *f, Packet *p)
+{
+    if (f->protoctx == NULL)
+        return;
+    FramesContainer *frames_container = AppLayerFramesGetContainer(f);
+    if (frames_container == NULL)
+        return;
+
+    Frames *frames;
+    TcpSession *ssn = f->protoctx;
+
+    if (ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED) {
+        AppLayerFramesFreeContainer(f);
+        return;
+    }
+
+    TcpStream *stream;
+    if (PKT_IS_TOSERVER(p)) {
+        stream = &ssn->client;
+        frames = &frames_container->toserver;
+    } else {
+        stream = &ssn->server;
+        frames = &frames_container->toclient;
+    }
+
+    const bool eof = ssn->state == TCP_CLOSED || PKT_IS_PSEUDOPKT(p) ||
+                     (ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED);
+    SCLogDebug("eof %s", eof ? "TRUE" : "false");
+    FramePrune(frames, stream, eof);
+}
diff --git a/src/app-layer-frames.h b/src/app-layer-frames.h
new file mode 100644 (file)
index 0000000..dce2cc3
--- /dev/null
@@ -0,0 +1,110 @@
+/* Copyright (C) 2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have frameeived a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Victor Julien <victor@inliniac.net>
+ */
+
+#ifndef __APP_LAYER_FRAMES_H__
+#define __APP_LAYER_FRAMES_H__
+
+#include "app-layer-events.h"
+#include "detect-engine-state.h"
+#include "util-file.h"
+#include "stream-tcp-private.h"
+#include "rust.h"
+#include "app-layer-parser.h"
+
+typedef int64_t FrameId;
+
+enum {
+    FRAME_FLAGE_TX_ID_SET,
+#define FRAME_FLAG_TX_ID_SET BIT_U8(FRAME_FLAGE_TX_ID_SET)
+    FRAME_FLAGE_ENDS_AT_EOF,
+#define FRAME_FLAG_ENDS_AT_EOF BIT_U8(FRAME_FLAGE_ENDS_AT_EOF)
+};
+
+typedef struct Frame {
+    uint8_t type;  /**< protocol specific field type. E.g. NBSS.HDR or SMB.DATA */
+    uint8_t flags; /**< frame flags: FRAME_FLAG_* */
+    uint8_t event_cnt;
+    // TODO one event per frame enough?
+    uint8_t events[4];  /**< per frame store for events */
+    int64_t rel_offset; /**< relative offset in the stream on top of Stream::stream_offset (if
+                           negative the start if before the stream data) */
+    int64_t len;
+    int64_t id;
+    uint64_t tx_id; /**< tx_id to match this frame. UINT64T_MAX if not used. */
+} Frame;
+// size 40
+
+#define FRAMES_STATIC_CNT 3
+
+typedef struct Frames {
+    uint16_t cnt;
+    uint16_t dyn_size;     /**< size in elements of `dframes` */
+    uint32_t progress_rel; /**< processing depth relative to STREAM_BASE_OFFSET */
+    uint64_t base_id;
+    uint32_t left_edge_rel;
+    Frame sframes[FRAMES_STATIC_CNT]; /**< static frames */
+    Frame *dframes;                   /**< dynamically allocated space for more frames */
+#ifdef DEBUG
+    uint8_t ipproto;
+    AppProto alproto;
+#endif
+} Frames;
+// size 136
+
+typedef struct FramesContainer {
+    Frames toserver;
+    Frames toclient;
+} FramesContainer;
+// size 272
+
+void FramesFree(Frames *frames);
+void FramesPrune(Flow *f, Packet *p);
+
+Frame *AppLayerFrameNewByPointer(Flow *f, const StreamSlice *stream_slice,
+        const uint8_t *frame_start, const int64_t len, int dir, uint8_t frame_type);
+Frame *AppLayerFrameNewByRelativeOffset(Flow *f, const StreamSlice *stream_slice,
+        const uint32_t frame_start_rel, const int64_t len, int dir, uint8_t frame_type);
+Frame *AppLayerFrameNewByAbsoluteOffset(Flow *f, const StreamSlice *stream_slice,
+        const uint64_t frame_start, const int64_t len, int dir, uint8_t frame_type);
+void AppLayerFrameDump(Flow *f);
+
+Frame *FrameGetByIndex(Frames *frames, const uint32_t idx);
+Frame *FrameGetById(Frames *frames, const int64_t id);
+
+Frame *AppLayerFrameGetById(Flow *f, const int direction, const FrameId frame_id);
+FrameId AppLayerFrameGetId(Frame *r);
+void AppLayerFrameAddEvent(Frame *frame, uint8_t e);
+void AppLayerFrameAddEventById(Flow *f, const int dir, const FrameId id, uint8_t e);
+void AppLayerFrameSetLength(Frame *frame, int64_t len);
+void AppLayerFrameSetLengthById(Flow *f, const int dir, const FrameId id, int64_t len);
+void AppLayerFrameSetTxId(Frame *r, uint64_t tx_id);
+void AppLayerFrameSetTxIdById(Flow *f, const int dir, const FrameId id, uint64_t tx_id);
+
+void AppLayerFramesUpdateProgress(
+        Flow *f, TcpStream *stream, const uint64_t progress, const uint8_t direction);
+void AppLayerFramesSlide(Flow *f, const uint32_t slide, const uint8_t direction);
+
+FramesContainer *AppLayerFramesGetContainer(Flow *f);
+FramesContainer *AppLayerFramesSetupContainer(Flow *f);
+
+#endif
index 806d5e249e918e718dec6a59e6a01a2f44f17d7c..e4fb82d2e1ee9d4a9a95d648653feb9ea76210ae 100644 (file)
@@ -165,8 +165,56 @@ struct AppLayerParserState_ {
 
     /* Used to store decoder events. */
     AppLayerDecoderEvents *decoder_events;
+
+    FramesContainer *frames;
 };
 
+static void AppLayerParserFramesFreeContainer(FramesContainer *frames)
+{
+    if (frames != NULL) {
+        FramesFree(&frames->toserver);
+        FramesFree(&frames->toclient);
+        SCFree(frames);
+    }
+}
+
+void AppLayerFramesFreeContainer(Flow *f)
+{
+    if (f == NULL || f->alparser == NULL || f->alparser->frames == NULL)
+        return;
+    AppLayerParserFramesFreeContainer(f->alparser->frames);
+    f->alparser->frames = NULL;
+}
+
+FramesContainer *AppLayerFramesGetContainer(Flow *f)
+{
+    if (f == NULL || f->alparser == NULL)
+        return NULL;
+    return f->alparser->frames;
+}
+
+FramesContainer *AppLayerFramesSetupContainer(Flow *f)
+{
+#ifdef UNITTESTS
+    if (f == NULL || f->alparser == NULL || f->protoctx == NULL)
+        return NULL;
+#endif
+    DEBUG_VALIDATE_BUG_ON(f == NULL || f->alparser == NULL);
+    if (f->alparser->frames == NULL) {
+        f->alparser->frames = SCCalloc(1, sizeof(FramesContainer));
+        if (f->alparser->frames == NULL) {
+            return NULL;
+        }
+#ifdef DEBUG
+        f->alparser->frames->toserver.ipproto = f->proto;
+        f->alparser->frames->toserver.alproto = f->alproto;
+        f->alparser->frames->toclient.ipproto = f->proto;
+        f->alparser->frames->toclient.alproto = f->alproto;
+#endif
+    }
+    return f->alparser->frames;
+}
+
 #ifdef UNITTESTS
 void UTHAppLayerParserStateGetIds(void *ptr, uint64_t *i1, uint64_t *i2, uint64_t *log, uint64_t *min)
 {
@@ -208,6 +256,7 @@ void AppLayerParserStateFree(AppLayerParserState *pstate)
 
     if (pstate->decoder_events != NULL)
         AppLayerDecoderEventsFreeEvents(&pstate->decoder_events);
+    AppLayerParserFramesFreeContainer(pstate->frames);
     SCFree(pstate);
 
     SCReturn;
index ec585ee3942526fc4bd1b308c192489c55e8b489..daeaa31431ec2607c5c5d2a0ee3ca3adafa4e4be 100644 (file)
@@ -26,6 +26,7 @@
 #define __APP_LAYER_PARSER_H__
 
 #include "app-layer-events.h"
+#include "app-layer-frames.h"
 #include "detect-engine-state.h"
 #include "util-file.h"
 #include "stream-tcp-private.h"
@@ -315,4 +316,6 @@ void AppLayerParserRestoreParserTable(void);
 void UTHAppLayerParserStateGetIds(void *ptr, uint64_t *i1, uint64_t *i2, uint64_t *log, uint64_t *min);
 #endif
 
+void AppLayerFramesFreeContainer(Flow *f);
+
 #endif /* __APP_LAYER_PARSER_H__ */
index 6fc0339e1fc38f7f8b04294acd8f1b87cf120bd2..dbe390e2df01511d7e8572738bfd94bfb239663e 100644 (file)
@@ -32,6 +32,7 @@
 #include "app-layer-expectation.h"
 #include "app-layer-ftp.h"
 #include "app-layer-detect-proto.h"
+#include "app-layer-frames.h"
 #include "stream-tcp-reassemble.h"
 #include "stream-tcp-private.h"
 #include "stream-tcp-inline.h"
index 984b81a875c6ca3bae31c0dea28f3925e4322c97..81c1cdd98cd48ff005d81a643004b64bb02bf3b2 100644 (file)
@@ -428,6 +428,7 @@ static void FlowWorkerFlowTimeout(ThreadVars *tv, Packet *p, FlowWorkerThreadDat
     /* Prune any stored files. */
     FlowPruneFiles(p);
 
+    FramesPrune(p->flow, p);
     /*  Release tcp segments. Done here after alerting can use them. */
     FLOWWORKER_PROFILING_START(p, PROFILE_FLOWWORKER_TCPPRUNE);
     StreamTcpPruneSession(p->flow, p->flowflags & FLOW_PKT_TOSERVER ?
@@ -568,6 +569,7 @@ static TmEcode FlowWorker(ThreadVars *tv, Packet *p, void *data)
                 StreamTcpSessionCleanup(p->flow->protoctx);
             }
         } else if (p->proto == IPPROTO_TCP && p->flow->protoctx) {
+            FramesPrune(p->flow, p);
             FLOWWORKER_PROFILING_START(p, PROFILE_FLOWWORKER_TCPPRUNE);
             StreamTcpPruneSession(p->flow, p->flowflags & FLOW_PKT_TOSERVER ?
                     STREAM_TOSERVER : STREAM_TOCLIENT);
index 5800251d1766ef3185ea135d5ca0b3b0921bdf44..cc9204abcf73243b85e5b4e5c4283ce260c3042d 100644 (file)
@@ -31,6 +31,7 @@
 #include "util-streaming-buffer.h"
 #include "util-print.h"
 #include "util-validate.h"
+#include "app-layer-frames.h"
 
 static void StreamTcpRemoveSegmentFromStream(TcpStream *stream, TcpSegment *seg);
 
@@ -676,7 +677,24 @@ static inline bool StreamTcpReturnSegmentCheck(const TcpStream *stream, const Tc
     SCReturnInt(true);
 }
 
-static inline uint64_t GetLeftEdge(TcpSession *ssn, TcpStream *stream)
+static inline uint64_t GetLeftEdgeForApp(Flow *f, TcpSession *ssn, TcpStream *stream)
+{
+    const FramesContainer *frames_container = AppLayerFramesGetContainer(f);
+    if (frames_container == NULL)
+        return STREAM_APP_PROGRESS(stream);
+
+    const Frames *frames =
+            stream == &ssn->client ? &frames_container->toserver : &frames_container->toclient;
+    //    const uint64_t x = FramesLeftEdge(stream, frames);
+    //  BUG_ON(x != (frames->left_edge_rel + STREAM_BASE_OFFSET(stream)));
+    //    return x;
+    const uint64_t o = (uint64_t)frames->left_edge_rel + STREAM_BASE_OFFSET(stream);
+    SCLogDebug(
+            "%s: frames left edge: %" PRIu64, &ssn->client == stream ? "toserver" : "toclient", o);
+    return o;
+}
+
+static inline uint64_t GetLeftEdge(Flow *f, TcpSession *ssn, TcpStream *stream)
 {
     bool use_app = true;
     bool use_raw = true;
@@ -694,6 +712,8 @@ static inline uint64_t GetLeftEdge(TcpSession *ssn, TcpStream *stream)
         use_log = false;
     }
 
+    SCLogDebug("use_app %d use_raw %d use_log %d", use_app, use_raw, use_log);
+
     if (use_raw) {
         uint64_t raw_progress = STREAM_RAW_PROGRESS(stream);
 
@@ -721,18 +741,19 @@ static inline uint64_t GetLeftEdge(TcpSession *ssn, TcpStream *stream)
         }
 
         if (use_app) {
-            left_edge = MIN(STREAM_APP_PROGRESS(stream), raw_progress);
-            SCLogDebug("left_edge %"PRIu64", using both app:%"PRIu64", raw:%"PRIu64,
-                    left_edge, STREAM_APP_PROGRESS(stream), raw_progress);
+            const uint64_t app_le = GetLeftEdgeForApp(f, ssn, stream);
+            left_edge = MIN(app_le, raw_progress);
+            SCLogDebug("left_edge %" PRIu64 ", using both app:%" PRIu64 ", raw:%" PRIu64, left_edge,
+                    app_le, raw_progress);
         } else {
             left_edge = raw_progress;
             SCLogDebug("left_edge %"PRIu64", using only raw:%"PRIu64,
                     left_edge, raw_progress);
         }
     } else if (use_app) {
-        left_edge = STREAM_APP_PROGRESS(stream);
-        SCLogDebug("left_edge %"PRIu64", using only app:%"PRIu64,
-                left_edge, STREAM_APP_PROGRESS(stream));
+        const uint64_t app_le = GetLeftEdgeForApp(f, ssn, stream);
+        left_edge = app_le;
+        SCLogDebug("left_edge %" PRIu64 ", using only app:%" PRIu64, left_edge, app_le);
     } else {
         left_edge = STREAM_BASE_OFFSET(stream) + stream->sb.buf_offset;
         SCLogDebug("no app & raw: left_edge %"PRIu64" (full stream)", left_edge);
@@ -847,10 +868,14 @@ void StreamTcpPruneSession(Flow *f, uint8_t flags)
         return;
     }
 
-    const uint64_t left_edge = GetLeftEdge(ssn, stream);
+    const uint64_t left_edge = GetLeftEdge(f, ssn, stream);
     if (left_edge && left_edge > STREAM_BASE_OFFSET(stream)) {
         uint32_t slide = left_edge - STREAM_BASE_OFFSET(stream);
         SCLogDebug("buffer sliding %u to offset %"PRIu64, slide, left_edge);
+
+        if (!(ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED)) {
+            AppLayerFramesSlide(f, slide, flags & (STREAM_TOSERVER | STREAM_TOCLIENT));
+        }
         StreamingBufferSlideToOffset(&stream->sb, left_edge);
         stream->base_seq += slide;
 
index ad2e30310d28e1873d11e59ab454a6cb81b20d00..99f0912d1d9ffcd6a01505d62767f7d26c89b0f6 100644 (file)
@@ -61,6 +61,7 @@
 #include "app-layer.h"
 #include "app-layer-events.h"
 #include "app-layer-parser.h"
+#include "app-layer-frames.h"
 
 #include "detect-engine-state.h"
 
@@ -1199,6 +1200,7 @@ static int ReassembleUpdateAppLayer (ThreadVars *tv,
         (void)AppLayerHandleTCPData(tv, ra_ctx, p, p->flow, ssn, stream,
                 (uint8_t *)mydata, mydata_len, flags);
         AppLayerProfilingStore(ra_ctx->app_tctx, p);
+        AppLayerFrameDump(p->flow);
         uint64_t new_app_progress = STREAM_APP_PROGRESS(*stream);
         if (new_app_progress == app_progress || FlowChangeProto(p->flow))
             break;
@@ -1388,6 +1390,14 @@ void StreamReassembleRawUpdateProgress(TcpSession *ssn, Packet *p, uint64_t prog
         stream = &ssn->server;
     }
 
+    /* Record updates */
+    if (!(ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED)) {
+        if (progress > STREAM_APP_PROGRESS(stream)) {
+            AppLayerFramesUpdateProgress(p->flow, stream, progress,
+                    PKT_IS_TOSERVER(p) ? STREAM_TOSERVER : STREAM_TOCLIENT);
+        }
+    }
+
     if (progress > STREAM_RAW_PROGRESS(stream)) {
         uint32_t slide = progress - STREAM_RAW_PROGRESS(stream);
         stream->raw_progress_rel += slide;