/*
 * 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.
 */

#include "squid.h"
#include "debug/Stream.h"
#include "LogTags.h"

void
LogTagsErrors::update(const LogTagsErrors &other)
{
    ignored = ignored || other.ignored;
    timedout = timedout || other.timedout;
    aborted = aborted || other.aborted;
}

LogTagsErrors
LogTagsErrors::FromErrno(const int errNo)
{
    LogTagsErrors lte;
    lte.timedout = (errNo == ETIMEDOUT);
    lte.aborted = !lte.timedout; // intentionally true for zero errNo
    return lte;
}

/* LogTags */

// old deprecated tag strings
const char * LogTags::Str_[] = {
    "TAG_NONE",
    "TCP_HIT",
    "TCP_MISS",
    "TCP_REFRESH_UNMODIFIED",
    "TCP_REFRESH_FAIL_OLD",
    "TCP_REFRESH_FAIL_ERR",
    "TCP_REFRESH_MODIFIED",
    "TCP_REFRESH",
    "TCP_CLIENT_REFRESH_MISS",
    "TCP_IMS_HIT",
    "TCP_INM_HIT",
    "TCP_SWAPFAIL_MISS",
    "TCP_NEGATIVE_HIT",
    "TCP_MEM_HIT",
    "TCP_DENIED",
    "TCP_DENIED_REPLY",
    "TCP_OFFLINE_HIT",
    "TCP_REDIRECT",
    "TCP_TUNNEL",
    "UDP_HIT",
    "UDP_MISS",
    "UDP_DENIED",
    "UDP_INVALID",
    "UDP_MISS_NOFETCH",
    "ICP_QUERY",
    "TYPE_MAX"
};

void
LogTags::update(const LogTags_ot t)
{
    assert(t < LOG_TYPE_MAX);
    debugs(83, 7, Str_[oldType] << " to " << Str_[t]);
    oldType = t;
}

/*
 * This method is documented in https://wiki.squid-cache.org/SquidFaq/SquidLogs#squid-result-codes
 * Please keep the wiki up to date
 */
const char *
LogTags::c_str() const
{
    static char buf[1024];
    *buf = 0;
    int pos = 0;

    // source tags
    const int protoLen = 3;
    if (oldType && oldType < LOG_TYPE_MAX) {
        assert(Str_[oldType][protoLen] == '_');
        snprintf(buf, protoLen + 1, "%s", Str_[oldType]);
        pos += protoLen;
    }
    else
        pos += snprintf(buf, sizeof(buf), "NONE");

    if (collapsingHistory.collapsed())
        pos += snprintf(buf + pos, sizeof(buf) - pos, "_CF");

    const char *tag = Str_[oldType] + protoLen;
    pos += snprintf(buf + pos, sizeof(buf) - pos, "%s", tag);

    if (err.ignored)
        pos += snprintf(buf+pos,sizeof(buf)-pos, "_IGNORED");

    // error tags
    if (err.timedout)
        pos += snprintf(buf+pos,sizeof(buf)-pos, "_TIMEDOUT");
    if (err.aborted)
        pos += snprintf(buf+pos,sizeof(buf)-pos, "_ABORTED");

    return buf;
}

bool
LogTags::isTcpHit() const
{
    return
        (oldType == LOG_TCP_HIT) ||
        (oldType == LOG_TCP_IMS_HIT) ||
        (oldType == LOG_TCP_INM_HIT) ||
        (oldType == LOG_TCP_REFRESH_FAIL_OLD) ||
        (oldType == LOG_TCP_REFRESH_UNMODIFIED) ||
        (oldType == LOG_TCP_NEGATIVE_HIT) ||
        (oldType == LOG_TCP_MEM_HIT) ||
        (oldType == LOG_TCP_OFFLINE_HIT);
}

const char *
LogTags::cacheStatusSource() const
{
    // see draft-ietf-httpbis-cache-header for the (quoted below) specs
    switch (oldType) {
    case LOG_TAG_NONE:
        return nullptr;

    case LOG_TCP_HIT:
    case LOG_TCP_IMS_HIT:
    case LOG_TCP_INM_HIT:
    case LOG_TCP_REFRESH_FAIL_OLD:
    case LOG_TCP_REFRESH_UNMODIFIED:
    case LOG_TCP_NEGATIVE_HIT:
    case LOG_TCP_MEM_HIT:
    case LOG_TCP_OFFLINE_HIT:
        // We put LOG_TCP_REFRESH_UNMODIFIED and LOG_TCP_REFRESH_FAIL_OLD here
        // because the specs probably classify master transactions where the
        // client request did "go forward" but the to-client response was
        // ultimately "obtained from the cache" as "hit" transactions.
        return ";hit";

    case LOG_TCP_MISS:
#if USE_DELAY_POOLS
        // do not lie until we get a better solution for bugs 1000, 2096
        return nullptr;
#else
        // TODO: "distinguish between uri-miss and vary-miss"
        return ";fwd=miss";
#endif

    case LOG_TCP_REFRESH_MODIFIED:
    case LOG_TCP_REFRESH:
        return ";fwd=stale";

    case LOG_TCP_CLIENT_REFRESH_MISS:
        return ";fwd=request";

    case LOG_TCP_REFRESH_FAIL_ERR:
    case LOG_TCP_SWAPFAIL_MISS:
        // Ignore "to be used when the implementation cannot distinguish between
        // uri-miss and vary-miss" specs condition as being too restrictive,
        // especially when there is no fwd=other or a more suitable parameter.
        return ";fwd=miss";

    case LOG_TCP_DENIED:
    case LOG_TCP_DENIED_REPLY:
    case LOG_TCP_REDIRECT:
        // We served a Squid-generated response (with or without forwarding).
        // The response itself should provide enough classification clues.
        return nullptr;

    case LOG_TCP_TUNNEL:
        // could use fwd=bypass, but the CONNECT request was not really bypassed
        return nullptr;

    case LOG_UDP_HIT:
    case LOG_UDP_MISS:
    case LOG_UDP_DENIED:
    case LOG_UDP_INVALID:
    case LOG_UDP_MISS_NOFETCH:
    case LOG_ICP_QUERY:
        // do not bother classifying these non-HTTP outcomes for now
        return nullptr;

    case LOG_TYPE_MAX:
        // should not happen
        return nullptr;
    }

    // should not happen
    return nullptr;
}

