/*
 * Copyright (C) 1996-2026 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 93    ICAP (RFC 3507) Client */

#include "squid.h"
#include "acl/FilledChecklist.h"
#include "adaptation/Answer.h"
#include "adaptation/icap/Config.h"
#include "adaptation/icap/Launcher.h"
#include "adaptation/icap/ServiceRep.h"
#include "adaptation/icap/Xaction.h"
#include "base/TextException.h"
#include "globals.h"
#include "HttpReply.h"

Adaptation::Icap::Launcher::Launcher(const char *aTypeName,
                                     Adaptation::ServicePointer &aService):
    AsyncJob(aTypeName),
    Adaptation::Initiate(aTypeName),
    theService(aService), theXaction(nullptr), theLaunches(0)
{
}

Adaptation::Icap::Launcher::~Launcher()
{
    assert(!theXaction);
}

void Adaptation::Icap::Launcher::start()
{
    Adaptation::Initiate::start();

    Must(theInitiator.set());
    launchXaction("first");
}

void Adaptation::Icap::Launcher::launchXaction(const char *xkind)
{
    Must(!theXaction);
    ++theLaunches;
    debugs(93,4, "launching " << xkind << " xaction #" << theLaunches);
    Adaptation::Icap::Xaction *x = createXaction();
    x->attempts = theLaunches;
    if (theLaunches > 1) {
        x->clearError();
        x->disableRetries();
    }
    if (theLaunches >= TheConfig.repeat_limit)
        x->disableRepeats("over icap_retry_limit");
    theXaction = initiateAdaptation(x);
    Must(initiated(theXaction));
}

void Adaptation::Icap::Launcher::noteAdaptationAnswer(const Answer &answer)
{
    debugs(93,5, "launches: " << theLaunches << " answer: " << answer);

    // XXX: akError is unused by ICAPXaction in favor of noteXactAbort()
    Must(answer.kind != Answer::akError);

    sendAnswer(answer);
    clearAdaptation(theXaction);
    Must(done());
}

void Adaptation::Icap::Launcher::noteInitiatorAborted()
{

    announceInitiatorAbort(theXaction); // propagate to the transaction
    clearInitiator();
    Must(done()); // should be nothing else to do

}

void Adaptation::Icap::Launcher::noteXactAbort(XactAbortInfo info)
{
    debugs(93,5, "theXaction:" << theXaction << " launches: " << theLaunches);

    // TODO: add more checks from FwdState::checkRetry()?
    if (canRetry(info)) {
        clearAdaptation(theXaction);
        launchXaction("retry");
    } else if (canRepeat(info)) {
        clearAdaptation(theXaction);
        launchXaction("repeat");
    } else {
        debugs(93,3, "cannot retry or repeat a failed transaction");
        clearAdaptation(theXaction);
        tellQueryAborted(false); // caller decides based on bypass, consumption
        Must(done());
    }
}

bool Adaptation::Icap::Launcher::doneAll() const
{
    return (!theInitiator || !theXaction) && Adaptation::Initiate::doneAll();
}

void Adaptation::Icap::Launcher::swanSong()
{
    if (theInitiator.set())
        tellQueryAborted(true); // always final here because abnormal

    if (theXaction.set())
        clearAdaptation(theXaction);

    Adaptation::Initiate::swanSong();
}

bool Adaptation::Icap::Launcher::canRetry(Adaptation::Icap::XactAbortInfo &info) const
{
    // We do not check and can exceed zero repeat limit when retrying.
    // This is by design as the limit does not apply to pconn retrying.
    return !shutting_down && info.isRetriable;
}

bool Adaptation::Icap::Launcher::canRepeat(Adaptation::Icap::XactAbortInfo &info) const
{
    debugs(93,9, shutting_down);
    if (theLaunches >= TheConfig.repeat_limit || shutting_down)
        return false;

    debugs(93,9, info.isRepeatable); // TODO: update and use status()
    if (!info.isRepeatable)
        return false;

    debugs(93,9, info.icapReply);
    if (!info.icapReply) // did not get to read an ICAP reply; a timeout?
        return true;

    debugs(93,9, info.icapReply->sline.status());
    // XXX: Http::scNone is not the only sign of parse error
    // XXX: if there is a specific HTTP error code describing the problem, that may be set
    if (info.icapReply->sline.status() == Http::scNone) // failed to parse the reply; I/O err
        return true;

    ACLFilledChecklist cl(TheConfig.repeat, info.icapRequest);
    cl.updateReply(info.icapReply);
    return cl.fastCheck().allowed();
}

/* ICAPXactAbortInfo */

Adaptation::Icap::XactAbortInfo::XactAbortInfo(HttpRequest *anIcapRequest,
        HttpReply *anIcapReply, bool beRetriable, bool beRepeatable):
    icapRequest(anIcapRequest),
    icapReply(anIcapReply),
    isRetriable(beRetriable),
    isRepeatable(beRepeatable)
{
    if (icapRequest)
        HTTPMSGLOCK(icapRequest);
    if (icapReply)
        HTTPMSGLOCK(icapReply);
}

Adaptation::Icap::XactAbortInfo::XactAbortInfo(const Adaptation::Icap::XactAbortInfo &i):
    icapRequest(i.icapRequest),
    icapReply(i.icapReply),
    isRetriable(i.isRetriable),
    isRepeatable(i.isRepeatable)
{
    if (icapRequest)
        HTTPMSGLOCK(icapRequest);
    if (icapReply)
        HTTPMSGLOCK(icapReply);
}

Adaptation::Icap::XactAbortInfo::~XactAbortInfo()
{
    HTTPMSGUNLOCK(icapRequest);
    HTTPMSGUNLOCK(icapReply);
}

