/* Copyright (C) 2015-2016 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 received 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.
 */

/*
 * This API is meant to be used with streaming data. A single memory
 * block is used to store the data. StreamingBufferSegment points to
 * chunk of data in the single StreamingBuffer. It points by offset
 * and length, so no pointers. The buffer is resized on demand and
 * slides forward, either automatically or manually.
 *
 * When a segment needs it's data it uses StreamingBufferSegmentGetData
 * which takes care of checking if the segment still has a valid offset
 * and length.
 *
 * The StreamingBuffer::stream_offset is an absolute offset since the
 * start of the data streaming.
 *
 * Similarly, StreamingBufferSegment::stream_offset is also an absolute
 * offset.
 *
 * Using the segments is optional.
 *
 *
 * stream_offset            buf_offset          stream_offset + buf_size
 * ^                        ^                   ^
 * |                        |                   |
 * |                        |                   |
 * +--------------------------------------------+
 * |         data           |     empty         |
 * |      xxxxxxxxxx        |                   |
 * +------^--------^--------+-------------------+
 *        |        |
 *        |        |
 *        |        |
 *        |        |
 *        |        |
 * +------+--------+-------+
 * | StreamingBufferSegment|
 * +-----------+-----------+
 * | offset    | len       |
 * +-----------+-----------+
 */

#ifndef SURICATA_UTIL_STREAMING_BUFFER_H
#define SURICATA_UTIL_STREAMING_BUFFER_H

#include "tree.h"

#define STREAMING_BUFFER_REGION_GAP_DEFAULT 262144

typedef struct StreamingBufferConfig_ {
    uint32_t buf_size;
    uint16_t max_regions; /**< max concurrent memory regions. 0 means no limit. */
    uint32_t region_gap;  /**< max gap size before a new region will be created. */
    void *(*Calloc)(size_t n, size_t size);
    void *(*Realloc)(void *ptr, size_t orig_size, size_t size);
    void (*Free)(void *ptr, size_t size);
} StreamingBufferConfig;

#define STREAMING_BUFFER_CONFIG_INITIALIZER                                                        \
    {                                                                                              \
        2048, 8, STREAMING_BUFFER_REGION_GAP_DEFAULT, NULL, NULL, NULL,                            \
    }

#define STREAMING_BUFFER_REGION_INIT                                                               \
    {                                                                                              \
        NULL, 0, 0, 0ULL, NULL,                                                                    \
    }

typedef struct StreamingBufferRegion_ {
    uint8_t *buf;           /**< memory block for reassembly */
    uint32_t buf_size;      /**< size of memory block */
    uint32_t buf_offset;    /**< how far we are in buf_size */
    uint64_t stream_offset; /**< stream offset of this region */
    struct StreamingBufferRegion_ *next;
} StreamingBufferRegion;

/**
 *  \brief block of continues data
 */
typedef struct StreamingBufferBlock {
    uint64_t offset;
    RB_ENTRY(StreamingBufferBlock) rb;
    uint32_t len;
} __attribute__((__packed__)) StreamingBufferBlock;

int SBBCompare(struct StreamingBufferBlock *a, struct StreamingBufferBlock *b);

/* red-black tree prototype for SACK records */
RB_HEAD(SBB, StreamingBufferBlock);
RB_PROTOTYPE(SBB, StreamingBufferBlock, rb, SBBCompare);
StreamingBufferBlock *SBB_RB_FIND_INCLUSIVE(struct SBB *head, StreamingBufferBlock *elm);

typedef struct StreamingBuffer_ {
    StreamingBufferRegion region;
    struct SBB sbb_tree;    /**< red black tree of Stream Buffer Blocks */
    StreamingBufferBlock *head; /**< head, should always be the same as RB_MIN */
    uint32_t sbb_size;          /**< data size covered by sbbs */
    uint16_t regions;
    uint16_t max_regions;
#ifdef DEBUG
    uint32_t buf_size_max;
#endif
} StreamingBuffer;

static inline bool StreamingBufferHasData(const StreamingBuffer *sb)
{
    return (sb->region.stream_offset || sb->region.buf_offset || sb->region.next != NULL ||
            !RB_EMPTY(&sb->sbb_tree));
}

static inline uint64_t StreamingBufferGetConsecutiveDataRightEdge(const StreamingBuffer *sb)
{
    return sb->region.stream_offset + sb->region.buf_offset;
}

static inline uint64_t StreamingBufferGetOffset(const StreamingBuffer *sb)
{
    return sb->region.stream_offset;
}

#ifndef DEBUG
#define STREAMING_BUFFER_INITIALIZER                                                               \
    {                                                                                              \
        STREAMING_BUFFER_REGION_INIT,                                                              \
        { NULL },                                                                                  \
        NULL,                                                                                      \
        0,                                                                                         \
        1,                                                                                         \
        1,                                                                                         \
    };
#else
#define STREAMING_BUFFER_INITIALIZER { STREAMING_BUFFER_REGION_INIT, { NULL }, NULL, 0, 1, 1, 0 };
#endif

typedef struct StreamingBufferSegment_ {
    uint32_t segment_len;
    uint64_t stream_offset;
} __attribute__((__packed__)) StreamingBufferSegment;

StreamingBuffer *StreamingBufferInit(const StreamingBufferConfig *cfg);
void StreamingBufferClear(StreamingBuffer *sb, const StreamingBufferConfig *cfg);
void StreamingBufferFree(StreamingBuffer *sb, const StreamingBufferConfig *cfg);

void StreamingBufferSlideToOffset(
        StreamingBuffer *sb, const StreamingBufferConfig *cfg, uint64_t offset);

int StreamingBufferAppend(StreamingBuffer *sb, const StreamingBufferConfig *cfg,
        StreamingBufferSegment *seg, const uint8_t *data, uint32_t data_len) WARN_UNUSED;
int StreamingBufferAppendNoTrack(StreamingBuffer *sb, const StreamingBufferConfig *cfg,
        const uint8_t *data, uint32_t data_len) WARN_UNUSED;
int StreamingBufferInsertAt(StreamingBuffer *sb, const StreamingBufferConfig *cfg,
        StreamingBufferSegment *seg, const uint8_t *data, uint32_t data_len,
        uint64_t offset) WARN_UNUSED;

void StreamingBufferSegmentGetData(const StreamingBuffer *sb,
                                   const StreamingBufferSegment *seg,
                                   const uint8_t **data, uint32_t *data_len);

void StreamingBufferSBBGetData(const StreamingBuffer *sb,
                               const StreamingBufferBlock *sbb,
                               const uint8_t **data, uint32_t *data_len);

void StreamingBufferSBBGetDataAtOffset(const StreamingBuffer *sb,
                                       const StreamingBufferBlock *sbb,
                                       const uint8_t **data, uint32_t *data_len,
                                       uint64_t offset);

int StreamingBufferSegmentCompareRawData(const StreamingBuffer *sb,
                                         const StreamingBufferSegment *seg,
                                         const uint8_t *rawdata, uint32_t rawdata_len);
int StreamingBufferCompareRawData(const StreamingBuffer *sb,
                                  const uint8_t *rawdata, uint32_t rawdata_len);

int StreamingBufferGetData(const StreamingBuffer *sb,
        const uint8_t **data, uint32_t *data_len,
        uint64_t *stream_offset);

int StreamingBufferGetDataAtOffset (const StreamingBuffer *sb,
        const uint8_t **data, uint32_t *data_len,
        uint64_t offset);

int StreamingBufferSegmentIsBeforeWindow(const StreamingBuffer *sb,
                                         const StreamingBufferSegment *seg);

void StreamingBufferRegisterTests(void);

#endif /* SURICATA_UTIL_STREAMING_BUFFER_H */
