/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
 *     Copyright 2014-2020 Couchbase, Inc.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

#ifndef NETBUF_H
#define NETBUF_H

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @file
 * @brief Netbuf write buffers
 */

/**
 * @defgroup netbufs Netbufs
 *
 * # Introduction
 *
 * ## GOALS
 *
 * 1.  provide a simple buffer allocation API
 *     From a logic perspective it's simplest to deal with a straight
 *     contiguous buffer per packet.
 *
 * 2.  provide an efficient way of sending multiple contiguous packets. This
 *     will reduce IOV fragmentation and reduce the number of trips to the
 *     I/O plugin for multiple writes. Currently this is done very efficiently
 *     with the ringbuffer - however this comes at the cost of copying all
 *     request data to the ringbuffer itself. Our aim is to reduce the
 *     number of copies while still maintaining a packed buffer.
 *
 * 3.  Allow a pluggable method by which user-provided data can be plugged
 *     into the span/cursor/flush architecture.
 *
 * @addtogroup netbufs
 * @{
 */

#include "sllist.h"
#include "netbuf-defs.h"
#include "netbuf-mblock.h"

/**
 * @brief Structure representing a buffer within netbufs
 *
 * @note It is recommended that you maintain the individual fields in your
 * own structure and then re-create them as needed. The span structure is 16
 * bytes on 64 bit systems, but can be reduced to 12 if needed. Additionally,
 * you may already have the 'size' field stored/calculated elsewhere.
 */
typedef struct {
    /** @private Parent block */
    nb_MBLOCK *parent;

    /** @private Offset from root at which this buffer begins */
    nb_SIZE offset;

    /** write-once: Allocation size */
    nb_SIZE size;
} nb_SPAN;

#define NETBUF_INVALID_OFFSET (nb_SIZE) - 1

/**
 * Creates a span from a buffer _not_ owned by netbufs.
 * @param span the span to initialize
 * @param buf the buffer
 * @param len the length of the buffer
 */
#define CREATE_STANDALONE_SPAN(span, buf, len)                                                                         \
    (span)->parent = (nb_MBLOCK *)(void *)buf;                                                                         \
    (span)->offset = NETBUF_INVALID_OFFSET;                                                                            \
    (span)->size = len;

/** @private */
typedef struct {
    sllist_node slnode;
    char *base;
    nb_SIZE len;
    /* Extra 4 bytes here. WHAT WE DO!!! */
    const void *parent; /* mc_PACKET */
} nb_SNDQELEM;

/** @private */
typedef struct {
    /** Linked list of pending spans to send */
    sllist_root pending;

    /**
     * List of PDUs to be flushed. A PDU is comprised of one or more IOVs
     * (or even a subsection thereof)
     */
    sllist_root pdus;

    /** The last window which was part of the previous fill call */
    nb_SNDQELEM *last_requested;

    /**
     * Number of bytes enqueued in the 'last request' element. This is needed
     * because it is possible for the last element to grow in length during
     * a subsequent flush.
     */
    nb_SIZE last_offset;

    /** Offset from last PDU which was partially flushed */
    nb_SIZE pdu_offset;

    /** Pool of elements to utilize */
    nb_MBPOOL elempool;
} nb_SENDQ;

struct netbuf_st {
    /** Send Queue */
    nb_SENDQ sendq;

    /** Pool for variable-size data */
    nb_MBPOOL datapool;

    nb_SETTINGS settings;
};

/**
 * Quick way to get the span from a buffer, when the buffer is *known* to
 * be standalone (i.e. CREATE_STANDALONE_SPAN()
 * @param span The span from which to extract the buffer
 * @return a pointer to the buffer
 */
#define SPAN_SABUFFER_NC(span) ((char *)(span)->parent)

/**
 * Quick way to get the span from a buffer when the buffer is known *not*
 * to be standalone
 * @param span The span from which to extract the buffer
 * @return A pointer to a buffer
 */
#define SPAN_MBUFFER_NC(span) ((span)->parent->root + (span)->offset)

/**
 * @brief Retrieves a pointer to the buffer related to this span.
 * @param span the span from which to extract the buffer
 * @return a pointer to the buffer.
 *
 * @see SPAN_SABUFFER_NC
 * @see SPAN_MBUFFER_NC
 */
#define SPAN_BUFFER(span) (((span)->offset == NETBUF_INVALID_OFFSET) ? SPAN_SABUFFER_NC(span) : SPAN_MBUFFER_NC(span))

/**
 * @brief allocate a span
 *
 * Reserve a contiguous region of memory, in-order for a given span. The
 * span will be reserved from the last block to be flushed to the network.
 *
 * The contents of the span are guaranteed to be contiguous (though not aligned)
 * and are available via the SPAN_BUFFER macro.
 *
 * @return 0 if successful, -1 on error
 */
int netbuf_mblock_reserve(nb_MGR *mgr, nb_SPAN *span);

/**
 * @brief release a span
 *
 * Release a span previously allocated via reserve_span. It is assumed that the
 * contents of the span have either:
 *
 * 1. been successfully sent to the network
 * 2. have just been scheduled (and are being removed due to error handling)
 * 3. have been partially sent to a connection which is being closed.
 *
 * @param mgr the manager in which this span is reserved
 * @param span the span
 */
void netbuf_mblock_release(nb_MGR *mgr, nb_SPAN *span);

/**
 * @brief Enqueue a span for serialization
 *
 * Schedules an IOV to be placed inside the send queue. The storage of the
 * underlying buffer must not be freed or otherwise modified until it has
 * been sent.
 *
 * With the current usage model, flush status is implicitly completed once
 * a response has arrived.
 *
 * Note that you may create the IOV from a SPAN object like so:
 * @code{.c}
 * iov->iov_len = span->size;
 * iov->iov_base = SPAN_BUFFER(span);
 * @endcode
 */
void netbuf_enqueue(nb_MGR *mgr, const nb_IOV *bufinfo, const void *parent);

void netbuf_enqueue_span(nb_MGR *mgr, nb_SPAN *span, const void *parent);

/**
 * Gets the number of IOV structures required to flush the entire contents of
 * all buffers.
 */
unsigned int netbuf_get_niov(nb_MGR *mgr);

/**
 * @brief
 * Populates an iovec structure for flushing a set of bytes from the various
 * blocks.
 *
 * You may call this function mutltiple times, so long as each call to
 * start_flush is eventually mapped with a call to end_flush.
 *
 * @code{.c}
 * netbuf_start_flush(mgr, iov1, niov1);
 * netbuf_start_flush(mgr, iov2, niov2);
 * ...
 * netbuf_end_flush(mgr, nbytes1);
 * netbuf_end_flush(mgr, nbytes2);
 * @endcode
 *
 * Additionally, only the LAST end_flush call may be supplied an nflushed
 * parameter which is smaller than the size returned by start_flush. If the
 * entire contents of the `iovs` structure cannot be flushed immediately and
 * you do not wish to persist it until such a time that it may be flushed, then
 * netbuf_reset_flush() should be called. See that functions' documentation
 * for more details.
 *
 * This function may be thought of advancing a virtual cursor which is mapped
 * to a send queue. Each time this function is called the cursor is advanced
 * by the number of bytes that the library expects you to flush (i.e. the return
 * value of this function). Typically the cursor is never rewound and any
 * operation that advances the cursor places the burden on the user to
 * actually flush the data contained within the IOV objects.
 * The netbuf_end_flush() function merely has the task of releasing any memory
 * used for mapping of already-flushed data.
 *
 * From a different perspective, each call to netbuf_start_flush() establishes
 * a contract between the library and the user: The library guarantees that
 * this specific region (referred to within the IOVs) will not be flushed by
 * any other subsystem (i.e. nothing else will try to flush the same data).
 * The user guarantees that the data will eventually be flushed, and that the
 * data will be flushed in the order it was received via start_flush().
 *
 *
 * @param mgr the manager object
 * @param iovs an array of iovec structures
 * @param niov the number of iovec structures allocated.
 * @param[out] nused how many IOVs are actually required
 *
 * @return the number of bytes which can be flushed in this IOV. If the
 * return value is 0 then there are no more bytes to flush.
 *
 * Note that the return value is limited by the number of IOV structures
 * provided and should not be taken as an indicator of how many bytes are
 * used overall.
 */
nb_SIZE netbuf_start_flush(nb_MGR *mgr, nb_IOV *iovs, int niov, int *nused);

/**
 * @brief Indicate that a flush has completed.
 *
 * Indicate that a number of bytes have been flushed. This should be called after
 * the data retrieved by get_flushing_iov has been flushed to the TCP buffers.
 *
 * @param mgr the manager object
 * @param nflushed how much data in bytes was flushed to the network.
 */
void netbuf_end_flush(nb_MGR *mgr, nb_SIZE nflushed);

/**
 * Reset the flush context for the buffer. This should be called only when the
 * following is true:
 *
 * (1) There is only a single open call to netbuf_start_flush
 * (2) The last call to end_flush did not pass all the bytes which were to
 *     be flushed.
 *
 * In this case, the next call to start_flush() will return an IOV which begins
 * where the last end_flush() finished, rather than the last start_flush().
 * As a consequence it means that the previous IOV populated with start_flush
 * is no longer valid and start_flush should be called again.
 */
#define netbuf_reset_flush(mgr)                                                                                        \
    do {                                                                                                               \
        (mgr)->sendq.last_requested = NULL;                                                                            \
        (mgr)->sendq.last_offset = 0;                                                                                  \
    } while (0);

/**
 * Informational function to get the total size of all data in the
 * buffers. This traverses all blocks, so call this for debugging only.
 */
nb_SIZE netbuf_get_size(const nb_MGR *mgr);

/**
 * Get the maximum size of a span which can be satisfied without using an
 * additional block.
 *
 * @param mgr
 *
 * @param allow_wrap
 * Whether to take into consideration wrapping. If this is true then the span
 * size will allow wrapping. If disabled, then only the packed size will be
 * available. Consider:
 * <pre>
 * [ ooooooo{S:10}xxxxxxxxx{C:10}ooooo{A:5} ]
 * </pre>
 * If wrapping is allowed, then the maximum span size will be 10, from 0..10
 * but the last 5 bytes at the end will be lost for the duration of the block.
 * If wrapping is not allowed then the maximum span size will be 5.
 *
 * @return
 * the maximum span size without requiring additional blocks.
 */
nb_SIZE netbuf_mblock_get_next_size(const nb_MGR *mgr, int allow_wrap);

/**
 * @brief Initializes an nb_MGR structure
 * @param mgr the manager to initialize
 * @param settings
 */
void netbuf_init(nb_MGR *mgr, const nb_SETTINGS *settings);

/**
 * @brief Frees up any allocated resources for a given manager
 * @param mgr the manager for which to release resources
 */
void netbuf_cleanup(nb_MGR *mgr);

/**
 * Populates the settings structure with the default settings. This structure
 * may then be modified or tuned and passed to netbuf_init()
 */
void netbuf_default_settings(nb_SETTINGS *settings);

/**
 * Dump the internal structure of the manager to the screen. Useful for
 * debugging.
 */
void netbuf_dump_status(nb_MGR *mgr, FILE *fp);

/**
 * Mark a PDU as being enqueued. This should be called whenever the final IOV
 * for a given PDU has just been enqueued.
 *
 * The PDU itself must remain valid and is assumed to contain an 'slnode'
 * structure which will point to the next PDU. The PDU will later be removed
 * from the queue when 'end_flush' has been called including its range.
 *
 * @param mgr The manager
 *
 * @param pdu An opaque pointer
 *
 * @param lloff The offset from the pdu pointer at which the slist_node
 *        structure may be found.
 */
void netbuf_pdu_enqueue(nb_MGR *mgr, void *pdu, nb_SIZE lloff);

/**
 * This callback is invoked during 'end_flush2'.
 *
 * @param pdu The PDU pointer enqueued via netbuf_pdu_enqueue()
 *
 * @param remaining A hint passed to the callback indicating how many bytes
 *        remain on the stream. IFF remaining is greater than the size of the
 *        PDU the callback may change internal state within the packet to mark
 *        it as flushed.
 *
 *        XXX:
 *        If nremaining < pdusize then it <b>must</b> be ignored. The value
 *        may have been previously passed to the same callback during a
 *        prior iteration.
 *
 *        This is done by design in order to avoid forcing each PDU to maintain
 *        a variable indicating "How much was flushed".
 *
 * @param arg A pointer passed to the start_flush call; used to correlate any
 *        data common to the queue itself.
 *
 * @return The size of the PDU. This will be used to determine future calls
 *         to the callback for subsequent PDUs.
 *
 *         If size <= remaining then this PDU will be popped off the PDU queue
 *         and is deemed to be no longer utilized by the send queue (and may
 *         be released from the mblock allocator; if it's being used).
 *
 *         If size > remaining then no further callbacks will be invoked on
 *         the relevant PDUs.
 */
typedef nb_SIZE (*nb_getsize_fn)(void *pdu, nb_SIZE remaining, void *arg);

void netbuf_end_flush2(nb_MGR *mgr, unsigned int nflushed, nb_getsize_fn callback, nb_SIZE lloff, void *arg);

/**
 * Ensures that the given internal structures of the manager are not allocated
 * or otherwise in use by other systems. This is useful for testing to ensure
 * that we wouldn't accidentally think everything is OK.
 *
 * Because resources are released at the block level, we might have had blocks
 * which were partially allocated.
 *
 * This also checks the PDU and send queues as well.
 */
int netbuf_is_clean(nb_MGR *mgr);

/**
 * Determines if there is any data to be flushed to the network
 */
int netbuf_has_flushdata(nb_MGR *mgr);

/**@}*/

#ifdef __cplusplus
}
#endif

#endif /* LCB_PACKET_H */
