/*
 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
 *
 * Squid software is distributed under GPLv2+ license and includes
 * contributions from numerous individuals and organizations.
 * Please see the COPYING and CONTRIBUTORS files for details.
 */

/* DEBUG: section 87    Client-side Stream routines. */

#include "squid.h"
#include "client_side_request.h"
#include "clientStream.h"
#include "http/Stream.h"
#include "HttpReply.h"
#include "HttpRequest.h"

/**
 \defgroup ClientStreamInternal Client Streams Internals
 \ingroup ClientStreamAPI
 \par
 * A client Stream is a uni directional pipe, with the usual non-blocking
 * asynchronous approach present elsewhere in squid.
 *
 \par
 * Each pipe node has a data push function, and a data request function.
 * This limits flexibility - the data flow is no longer assembled at each
 * step.
 *
 \par
 * An alternative approach is to pass each node in the pipe the call-
 * back to use on each IO call. This allows the callbacks to be changed
 * very easily by a participating node, but requires more maintenance
 * in each node (store the callback to the most recent IO request in
 * the nodes context.) Such an approach also prevents dynamically
 * changing the pipeline from outside without an additional interface
 * method to extract the callback and context from the next node.
 *
 \par
 * One important characteristic of the stream is that the readfunc
 * on the terminating node, and the callback on the first node
 * will be NULL, and never used.
 *
 \section QuickNotes Quick Notes
 \par
 * Each node including the HEAD of the clientStream has a cbdataReference
 * held by the stream. Freeing the stream then removes that reference
 * and delete's every node.
 * Any node with other References, and all nodes downstream will only
 * free when those references are released.
 * Stream nodes MAY hold references to the data member of the node.
 *
 \par
 * Specifically - on creation no reference is made.
 * If you pass a data variable to a node, give it an initial reference.
 * If the data member is non-null on FREE, cbdataFree WILL be called.
 * This you must never call cbdataFree on your own context without
 * explicitly setting the stream node data member to NULL and
 * cbdataReferenceDone'ing it.
 *
 \par
 * No data member may hold a reference to it's stream node.
 * The stream guarantees that DETACH will be called before
 * freeing the node, allowing data members to cleanup.
 *
 \par
 * If a node's data holds a reference to something that needs to
 * free the stream a circular reference list will occur.
 * This results no data being freed until that reference is removed.
 * One way to accomplish thisObject is to explicitly remove the
 * data from your own node before freeing the stream.
 *
 \code
   mycontext = thisObject->data;
   thisObject->data = nullptr;
   delete thisObject->head;
   mycontext = nullptr;
   return;
 \endcode
 *
 * TODO: rather than each node undeleting the next, have a clientStreamDelete that walks the list.
 */

CBDATA_CLASS_INIT(clientStreamNode);

clientStreamNode::clientStreamNode(CSR * aReadfunc, CSCB * aCallback, CSD * aDetach, CSS * aStatus, ClientStreamData aData) :
    head(nullptr),
    readfunc(aReadfunc),
    callback(aCallback),
    detach(aDetach),
    status(aStatus),
    data(aData)
{}

clientStreamNode::~clientStreamNode()
{
    debugs(87, 3, "Freeing clientStreamNode " << this);

    removeFromStream();
    data = nullptr;
}

/**
 \ingroup ClientStreamInternal
 * Initialise a client Stream.
 * list is the stream
 * func is the read function for the head
 * callback is the callback for the tail
 * tailbuf and taillen are the initial buffer and length for the tail.
 */
void
clientStreamInit(dlink_list * list, CSR * func, CSD * rdetach, CSS * readstatus,
                 ClientStreamData readdata, CSCB * callback, CSD * cdetach, ClientStreamData callbackdata,
                 StoreIOBuffer tailBuffer)
{
    clientStreamNode *temp = new clientStreamNode(func, nullptr, rdetach, readstatus, readdata);
    dlinkAdd(cbdataReference(temp), &temp->node, list);
    temp->head = list;
    clientStreamInsertHead(list, nullptr, callback, cdetach, nullptr, callbackdata);
    temp = (clientStreamNode *)list->tail->data;
    temp->readBuffer = tailBuffer;
}

/**
 \ingroup ClientStreamInternal
 * Doesn't actually insert at head. Instead it inserts one *after*
 * head. This is because HEAD is a special node, as is tail
 * This function is not suitable for inserting the real HEAD.
 */
void
clientStreamInsertHead(dlink_list * list, CSR * func, CSCB * callback,
                       CSD * detach, CSS * status, ClientStreamData data)
{
    /* test preconditions */
    assert(list != nullptr);
    assert(list->head);
    clientStreamNode *temp = new clientStreamNode(func, callback, detach, status, data);
    temp->head = list;
    debugs(87, 3, "clientStreamInsertHead: Inserted node " << temp <<
           " with data " << data.getRaw() << " after head");

    if (list->head->next)
        temp->readBuffer = ((clientStreamNode *)list->head->next->data)->readBuffer;

    dlinkAddAfter(cbdataReference(temp), &temp->node, list->head, list);
}

// API
void
clientStreamCallback(clientStreamNode * thisObject, ClientHttpRequest * http,
                     HttpReply * rep, StoreIOBuffer replyBuffer)
{
    clientStreamNode *next;
    assert(thisObject && http && thisObject->node.next);
    next = thisObject->next();

    debugs(87, 3, thisObject << " gives " << next->data << ' ' << replyBuffer);
    next->callback(next, http, rep, replyBuffer);
}

/**
 \ingroup ClientStreamInternal
 * Call the previous node in the chain to read some data
 *
 \param thisObject  ??
 \param http        ??
 \param readBuffer  ??
 */
void
clientStreamRead(clientStreamNode * thisObject, ClientHttpRequest * http,
                 StoreIOBuffer readBuffer)
{
    /* place the parameters on the 'stack' */
    clientStreamNode *prev;
    assert(thisObject && http && thisObject->prev());
    prev = thisObject->prev();

    debugs(87, 3, "clientStreamRead: Calling " << prev->readfunc <<
           " with cbdata " << prev->data.getRaw() << " from node " << thisObject);
    thisObject->readBuffer = readBuffer;
    prev->readfunc(prev, http);
}

/**
 \ingroup ClientStreamInternal
 * Detach from the stream - only allowed for terminal members
 *
 \param thisObject  ??
 \param http        ??
 */
void
clientStreamDetach(clientStreamNode * thisObject, ClientHttpRequest * http)
{
    clientStreamNode *temp = thisObject;

    assert(thisObject->node.next == nullptr);
    debugs(87, 3, "clientStreamDetach: Detaching node " << thisObject);
    /* And clean up thisObject node */
    /* ESI TODO: push refcount class through to head */
    clientStreamNode *prev = nullptr;

    if (thisObject->prev())
        prev = cbdataReference(thisObject->prev());

    thisObject->removeFromStream();

    cbdataReferenceDone(temp);

    delete thisObject;

    /* and tell the prev that the detach has occurred */
    /*
     * We do it in thisObject order so that the detaching node is always
     * at the end of the list
     */

    if (prev) {
        debugs(87, 3, "clientStreamDetach: Calling " << prev->detach << " with cbdata " << prev->data.getRaw());

        if (cbdataReferenceValid(prev))
            prev->detach(prev, http);

        cbdataReferenceDone(prev);
    }
}

/**
 \ingroup ClientStreamInternal
 * Abort the stream - detach every node in the pipeline.
 *
 \param thisObject  ??
 \param http        ??
 */
void
clientStreamAbort(clientStreamNode * thisObject, ClientHttpRequest * http)
{
    dlink_list *list;

    assert(thisObject != nullptr);
    assert(http != nullptr);
    list = thisObject->head;
    debugs(87, 3, "clientStreamAbort: Aborting stream with tail " << list->tail);

    if (list->tail) {
        clientStreamDetach((clientStreamNode *)list->tail->data, http);
    }
}

/**
 \ingroup ClientStreamInternal
 * Call the upstream node to find it's status
 *
 \param thisObject  ??
 \param http        ??
 */
clientStream_status_t
clientStreamStatus(clientStreamNode * thisObject, ClientHttpRequest * http)
{
    clientStreamNode *prev;
    assert(thisObject && http && thisObject->node.prev);
    prev = (clientStreamNode *)thisObject->node.prev->data;
    return prev->status(prev, http);
}

void
clientStreamNode::removeFromStream()
{
    if (head)
        dlinkDelete(&node, head);

    head = nullptr;
}

clientStreamNode *
clientStreamNode::prev() const
{
    if (node.prev)
        return (clientStreamNode *)node.prev->data;
    else
        return nullptr;
}

clientStreamNode *
clientStreamNode::next() const
{
    if (node.next)
        return (clientStreamNode *)node.next->data;
    else
        return nullptr;
}

