diff --git a/.asf.yaml b/.asf.yaml
index eb33ef24fe1..552b11bc0e0 100644
--- a/.asf.yaml
+++ b/.asf.yaml
@@ -38,6 +38,9 @@ github:
- lzx404243
- phongn
- jasmine-nahrain
+ copilot_code_review:
+ enabled: true
+ review_on_push: true
protected_branches:
master:
required_status_checks:
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index efc00f8ff41..266971f45d9 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -23,6 +23,7 @@ jobs:
name: Analyze
runs-on: ubuntu-latest
permissions:
+ packages: read
actions: read
contents: read
security-events: write
@@ -30,9 +31,9 @@ jobs:
strategy:
fail-fast: false
matrix:
- language: [ 'cpp' ]
- # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
- # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
+ include:
+ - language: c-cpp
+ build-mode: manual
steps:
- name: Checkout repository
@@ -43,9 +44,10 @@ jobs:
sudo apt install libmagick++-dev libncurses-dev libpcre2-dev libbrotli-dev libluajit-5.1-dev luajit libjansson-dev libcjose-dev libmaxminddb-dev libgeoip-dev ninja-build cmake libpcre3-dev
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@v3
+ uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
+ build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
@@ -65,11 +67,13 @@ jobs:
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
- - run: |
+ - name: Run manual build steps
+ shell: bash
+ run: |
echo "Run, Build Application using script"
cmake -B build --preset ci
cmake --build build -v
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v3
+ uses: github/codeql-action/analyze@v4
with:
category: "/language:${{matrix.language}}"
diff --git a/AGENTS.md b/AGENTS.md
index 75308cecdfb..e045c3c648f 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -123,6 +123,13 @@ regularly).
using the Proxy Verifier format. This is simpler, more maintainable, and
parseable by tools.
+**Python conventions for test and helper scripts:**
+- Launch Python helpers with `{sys.executable}` rather than a hardcoded `python3`,
+ so the test runs under the same interpreter the harness uses.
+- Prefer f-strings over `str.format()` when building command lines, config lines,
+ and `Testers` expressions.
+- Add type annotations to helper functions.
+
**For complete details on writing autests, see:**
- `doc/developer-guide/testing/autests.en.rst` - Comprehensive guide to autest
- Proxy Verifier format: https://github.com/yahoo/proxy-verifier
@@ -383,6 +390,11 @@ MIOBuffer *buffer = (MIOBuffer*)malloc(sizeof(MIOBuffer));
- `src/proxy/http/remap/RemapConfig.cc` - URL remapping logic
- `include/ts/ts.h` - Plugin API
+## Security
+
+See [SECURITY.md](SECURITY.md) for the project's security policy, threat model,
+scope, and vulnerability reporting process.
+
## Resources
- Official docs: https://trafficserver.apache.org/
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 923651059f5..d28c3bd289c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -166,9 +166,12 @@ option(ENABLE_AUTEST_UDS "Setup autest with curl using UDS (default OFF)")
option(ENABLE_BENCHMARKS "Build benchmarks (default OFF)")
option(EXTERNAL_YAML_CPP "Use external yaml-cpp (default OFF)")
option(EXTERNAL_LIBSWOC "Use external libswoc (default OFF)")
+option(EXTERNAL_HWY "Use external highway (default OFF)")
option(LINK_PLUGINS "Link core libraries to plugins (default OFF)")
option(ENABLE_PROBES "Enable ATS SystemTap probes (default OFF)")
option(ENABLE_VERIFY_PLUGINS "Enable plugin verification tests (default ON)" ON)
+option(ENABLE_THREAD_SAFETY_ANALYSIS "Enable Clang -Wthread-safety analysis (Clang only, default ON)" ON)
+option(THREAD_SAFETY_ANALYSIS_AS_ERROR "Treat thread-safety findings as errors; for CI gating (default OFF)")
# Setup user
# NOTE: this is the user trafficserver runs as
@@ -358,6 +361,35 @@ elseif(ENABLE_TSAN)
add_link_options(-g -fsanitize=thread)
endif()
+# Clang Thread Safety Analysis. The TS_* annotations (tsutil/ts_thread_safety.h)
+# compile to nothing on GCC, and the flag is Clang-only on purpose (GCC does not
+# know -Wthread-safety and would itself error if passed it), so this whole block
+# is a no-op for GCC builds.
+#
+# On Clang it is on by default but only a WARNING: -Wno-error=thread-safety keeps
+# it a warning even in builds that otherwise use -Werror, so an in-progress
+# annotation never blocks a developer's build. CI sets
+# THREAD_SAFETY_ANALYSIS_AS_ERROR=ON to promote findings to errors and gate
+# merges.
+#
+# Skip FreeBSD: its libc annotates the pthread primitives themselves, so
+# -Wthread-safety there flags ATS's existing hand-rolled mutex wrappers
+# (tscore/ink_mutex.h, ink_rwlock, ...) tree-wide, not just newly-annotated code.
+# Enabling it on FreeBSD needs those legacy wrappers made analysis-clean first.
+# Elsewhere the platform leaves the pthread primitives un-annotated, so only
+# annotated code is analyzed and this stays quiet until a real violation.
+if(ENABLE_THREAD_SAFETY_ANALYSIS
+ AND CMAKE_CXX_COMPILER_ID MATCHES "Clang"
+ AND NOT CMAKE_SYSTEM_NAME STREQUAL "FreeBSD"
+)
+ add_compile_options(-Wthread-safety)
+ if(THREAD_SAFETY_ANALYSIS_AS_ERROR)
+ add_compile_options(-Werror=thread-safety)
+ else()
+ add_compile_options(-Wno-error=thread-safety)
+ endif()
+endif()
+
if(ENABLE_PROBES)
add_compile_options("-DENABLE_SYSTEMTAP_PROBES")
endif()
@@ -415,6 +447,11 @@ if(EXTERNAL_LIBSWOC)
find_package(libswoc REQUIRED)
endif()
+if(EXTERNAL_HWY)
+ message(STATUS "Looking for external highway")
+ find_package(HWY "1.4.0" CONFIG REQUIRED)
+endif()
+
include(Check128BitCas)
include(ConfigureTransparentProxy)
@@ -541,6 +578,8 @@ check_symbol_exists(SSL_error_description "openssl/ssl.h" HAVE_SSL_ERROR_DESCRIP
check_symbol_exists(SSL_CTX_set_ciphersuites "openssl/ssl.h" TS_USE_TLS_SET_CIPHERSUITES)
check_symbol_exists(SSL_CTX_set_keylog_callback "openssl/ssl.h" TS_HAS_TLS_KEYLOGGING)
check_symbol_exists(SSL_CTX_set_tlsext_ticket_key_cb "openssl/ssl.h" HAVE_SSL_CTX_SET_TLSEXT_TICKET_KEY_CB)
+check_symbol_exists(SSL_CTX_add_cert_compression_alg "openssl/ssl.h" HAVE_SSL_CTX_ADD_CERT_COMPRESSION_ALG)
+check_symbol_exists(SSL_CTX_set1_cert_comp_preference "openssl/ssl.h" HAVE_SSL_CTX_SET1_CERT_COMP_PREFERENCE)
check_symbol_exists(SSL_get_all_async_fds openssl/ssl.h TS_USE_TLS_ASYNC)
check_symbol_exists(OSSL_PARAM_construct_end "openssl/params.h" HAVE_OSSL_PARAM_CONSTRUCT_END)
check_symbol_exists(TLS1_3_VERSION "openssl/ssl.h" TS_USE_TLS13)
@@ -674,8 +713,11 @@ if(ENABLE_AUTEST)
# the autest command. The original AUTEST_OPTIONS string is used in the
# autest.sh script.
separate_arguments(AUTEST_OPTIONS_LIST UNIX_COMMAND "${AUTEST_OPTIONS}")
- set(PROXY_VERIFIER_VERSION "v3.1.2")
- set(PROXY_VERIFIER_HASH "SHA1=0a60c646cbc9326abb2fbc397cb9efa8c08a807a")
+ file(READ "${CMAKE_SOURCE_DIR}/tests/proxy-verifier-version.txt" PROXY_VERIFIER_VERSION)
+ string(STRIP "${PROXY_VERIFIER_VERSION}" PROXY_VERIFIER_VERSION)
+ file(READ "${CMAKE_SOURCE_DIR}/tests/proxy-verifier-checksum.txt" PROXY_VERIFIER_SHA1)
+ string(STRIP "${PROXY_VERIFIER_SHA1}" PROXY_VERIFIER_SHA1)
+ set(PROXY_VERIFIER_HASH "SHA1=${PROXY_VERIFIER_SHA1}")
include(proxy-verifier)
endif()
diff --git a/CMakePresets.json b/CMakePresets.json
index 3fe7e010b85..cec863644f3 100644
--- a/CMakePresets.json
+++ b/CMakePresets.json
@@ -125,6 +125,8 @@
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_COMPILE_WARNING_AS_ERROR": "ON",
+ "ENABLE_THREAD_SAFETY_ANALYSIS": "ON",
+ "THREAD_SAFETY_ANALYSIS_AS_ERROR": "ON",
"ENABLE_CCACHE": "ON",
"BUILD_EXPERIMENTAL_PLUGINS": "ON",
"ENABLE_WASM_WAMR": "OFF",
@@ -194,13 +196,9 @@
"name": "ci-fedora-cxx20",
"displayName": "CI Fedora c++20",
"description": "CI Pipeline config for Fedora Linux compiled with c++20",
- "inherits": ["ci"],
+ "inherits": ["ci-fedora"],
"cacheVariables": {
- "opentelemetry_ROOT": "/opt",
- "CURL_ROOT": "/opt",
- "wamr_ROOT": "/opt",
- "CMAKE_CXX_STANDARD": "20",
- "ENABLE_CRIPTS": "ON"
+ "CMAKE_CXX_STANDARD": "20"
}
},
{
@@ -450,4 +448,3 @@
}
]
}
-
diff --git a/NOTICE b/NOTICE
index 31787fecbf0..e1537312911 100644
--- a/NOTICE
+++ b/NOTICE
@@ -95,6 +95,7 @@ https://github.com/jbeder/yaml-cpp
~~
fastlz: an ANSI C/C90 implementation of Lempel-Ziv 77 algorithm (LZ77) of lossless data compression.
+Copyright (C) 2005-2020 Ariya Hidayat (MIT License)
https://github.com/ariya/FastLZ
~~
@@ -118,3 +119,17 @@ LS-HPACK provides functionality to encode and decode HTTP headers using
HPACK compression mechanism specified in RFC 7541.
Copyright (c) 2018 - 2023 LiteSpeed Technologies Inc, (MIT License)
https://github.com/litespeedtech/ls-hpack.git
+
+~~
+
+S3-FIFO: A simple, scalable FIFO-based algorithm with three static queues
+Copyright 2023, Carnegie Mellon University (Apache-2.0)
+
+Website: https://s3fifo.com/
+Paper: http://dx.doi.org/10.1145/3600006.3613147
+Reference implementation: https://github.com/Thesys-lab/sosp23-s3fifo
+
+~~
+
+Highway is a C++ library that provides portable SIMD/vector intrinsics.
+https://github.com/google/highway
diff --git a/README.md b/README.md
index 46d5eaf7533..b14b0245273 100644
--- a/README.md
+++ b/README.md
@@ -143,6 +143,7 @@ trafficserver ............. Top src dir
├── lib ................... Third-party libraries
│ ├── Catch2 ............ Unit testing framework
│ ├── fastlz ............ Fast compression library
+│ ├── highway ........... Portable SIMD/vector intrinsics
│ ├── ls-hpack .......... HPACK compression for HTTP/2
│ ├── swoc .............. Solid Wall of Code utility library
│ ├── systemtap ......... SystemTap integration
diff --git a/SECURITY.md b/SECURITY.md
index be75009149c..8d46386e9be 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -16,7 +16,11 @@ Administrative users are always considered to be trusted. Reports for vulnerabil
Security-sensitive information may be logged with modified logging configurations, particularly if debug logging is enabled.
-Experimental features and plugins are known unstable and not supposed to be used on production. We do not consider
-vulnerabilities in those as security issues. You may report vulnerabilities in those publicly on our public lists or GitHub. However, please
+Experimental features are known unstable and not supposed to be used on production. We do not consider
+vulnerabilities in those as security issues. This explicitly includes HTTP/3 and QUIC support, which remain
+experimental. You may report vulnerabilities in those publicly on our public lists or GitHub. However, please
contact us privately, if you believe the vulnerabilities you find are serious, or if you are not sure whether you should report the
vulnerabilities publicly.
+
+Plugins shipped with Traffic Server, including those under `plugins/experimental/`, are in scope for security
+reporting. Please report vulnerabilities in those through the private security mailing list following the process above.
diff --git a/ci/coverage b/ci/coverage
index 29b9aac0b48..97a83ffbbaf 100755
--- a/ci/coverage
+++ b/ci/coverage
@@ -16,93 +16,111 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-LCOV=${LCOV:-lcov}
-GENHTML=${GENHTML:-genhtml}
-
-TMPDIR=${TMPDIR:-/tmp}
-BUILDID="org.apache.trafficserver.$$"
-
-SRCROOT=${SRCROOT:-$(cd $(dirname $0)/.. && pwd)} # where the source lives
-OBJROOT=${OBJROOT:-"$TMPDIR/$BUILDID/obj"} # where we are building
-DSTROOT=${DSTROOT:-"$TMPDIR/$BUILDID/dst"} # where we are installing
-
-# Force low make parallelization so that the build can complete in a VM with
-# only a small amount of memory.
-NPROCS=${NPROCS:-2}
-
-mkdir -p $SRCROOT
-mkdir -p $OBJROOT
-mkdir -p $DSTROOT
-
-autogen() {
- (
- cd "$SRCROOT"
- [ configure -nt configure.ac -a Makefile.in -nt Makefile.am ] || autoreconf -fi
- )
-}
+# Build ATS with gcov instrumentation, run the unit tests (and optionally
+# autests), and produce a coverage report with gcovr.
+#
+# Usage:
+# ci/coverage [extra cmake args...]
+#
+# Environment overrides:
+# BUILDDIR build tree (default: $SRCROOT/build-coverage)
+# PREFIX install prefix (default: /tmp/ts-coverage)
+# NPROCS build parallelism (default: nproc)
+# GCOVR gcovr command (default: gcovr, falls back to uvx gcovr)
+# AUTEST run autests too if set to a -f/--filter basename glob,
+# e.g. AUTEST='tls_*' or AUTEST=all for the whole suite
+#
+# Requires: cmake, ninja or make, gcc/gcov, and gcovr (pip install gcovr).
+
+set -e
+
+SRCROOT=${SRCROOT:-$(cd "$(dirname "$0")"/.. && pwd)}
+BUILDDIR=${BUILDDIR:-$SRCROOT/build-coverage}
+PREFIX=${PREFIX:-/tmp/ts-coverage}
+NPROCS=${NPROCS:-$(nproc 2>/dev/null || echo 4)}
+
+GCOVR=${GCOVR:-gcovr}
+if ! command -v "${GCOVR%% *}" >/dev/null 2>&1; then
+ if command -v uvx >/dev/null 2>&1; then
+ GCOVR="uvx gcovr"
+ else
+ echo "gcovr not found; install it with 'pip install gcovr'" >&2
+ exit 1
+ fi
+fi
+
+# --coverage instruments compile and link. Atomic profile updates keep the
+# counters sane in ATS's heavily threaded runtime, and absolute paths in the
+# notes files let gcovr resolve sources from any working directory.
+COVERAGE_FLAGS="-g -O0 --coverage -fprofile-update=atomic -fprofile-abs-path"
configure() {
- (
- cd $OBJROOT
- $SRCROOT/configure \
- --prefix=$DSTROOT \
- --enable-debug \
- --enable-coverage \
- --enable-werror \
- --enable-example-plugins \
- --enable-test-tools \
- --enable-experimental-plugins \
- CC="$CC" \
- CXX="$CXX" \
- "$@"
- )
+ # Enabling autest pulls in Python3/uv/nc and the proxy-verifier toolchain at
+ # configure time, so only ask for it when AUTEST actually selects a run.
+ local autest_args=()
+ if [ -n "$AUTEST" ]; then
+ autest_args=(-DENABLE_AUTEST=ON)
+ fi
+
+ cmake -S "$SRCROOT" -B "$BUILDDIR" \
+ -DCMAKE_BUILD_TYPE=Debug \
+ -DCMAKE_INSTALL_PREFIX="$PREFIX" \
+ -DCMAKE_COMPILE_WARNING_AS_ERROR=OFF \
+ -DBUILD_TESTING=ON \
+ "${autest_args[@]}" \
+ -DBUILD_EXPERIMENTAL_PLUGINS=ON \
+ -DENABLE_EXAMPLE=ON \
+ -DCMAKE_CXX_FLAGS_DEBUG="$COVERAGE_FLAGS" \
+ -DCMAKE_C_FLAGS_DEBUG="$COVERAGE_FLAGS" \
+ "$@"
}
build() {
- ( cd $OBJROOT && $MAKE -j $NPROCS )
- ( cd $OBJROOT && $MAKE install )
+ cmake --build "$BUILDDIR" -j "$NPROCS"
+ cmake --install "$BUILDDIR"
}
-regress() {
- ( cd $OBJROOT && $MAKE check ) && \
- $DSTROOT/bin/traffic_server -k -K -R 1
+run_tests() {
+ # Reset counters so the report reflects exactly this run.
+ find "$BUILDDIR" -name '*.gcda' -delete
+
+ ctest --test-dir "$BUILDDIR" -j "$NPROCS"
+
+ if [ -n "$AUTEST" ]; then
+ local filter_args=()
+ if [ "$AUTEST" != all ]; then
+ # Quote the pattern: the parallel runner fnmatches it against each test's
+ # basename (e.g. 'tls_*'), so it must reach autest verbatim rather than be
+ # glob-expanded by the shell first.
+ filter_args=(-f "$AUTEST")
+ fi
+ # Match ctest's parallelism: -j routes autest.sh through autest-parallel.py,
+ # which already aims --sandbox at a per-build-tree path under /tmp, so
+ # concurrent coverage runs in separate build trees don't trample each other.
+ (cd "$BUILDDIR/tests" && ./autest.sh -j "$NPROCS" "${filter_args[@]}")
+ fi
}
-CC=${CC:-gcc}
-CXX=${CXX:-g++}
-MAKE=${MAKE:-make}
-export CC CXX MAKE
-
-case $VERBOSE in
- Y*) set -x ;;
- y*) set -x ;;
- 1) set -x ;;
- *) set +x ;;
-esac
-
-autogen || exit 1
-configure "$@" || exit 1
-build || exit 1
-
-$LCOV --quiet --capture --initial --directory $OBJROOT --output-file initial.info
-
-regress
-
-$LCOV --quiet --capture --directory $OBJROOT --output-file tests.info
-
-# The --add-tracefile option refuses to create an output file with
-# --output-file (contrary to documentation). Capture the combined
-# coverage from stdout instead.
-$LCOV \
- --add-tracefile initial.info \
- --add-tracefile tests.info \
-> combined.info
-
-# genhtml will puke because it can't find the original TSConfig files.
-# We don't need to bother generation anything for /usr/include.
-$LCOV --remove combined.info \
- 'TsConfigSyntax.*' \
- 'TsConfigGrammar.*' \
- '/usr/include/*' > coverage.info
+report() {
+ cd "$SRCROOT"
+ # no_working_dir_found: vendored/generated objects whose recorded cwd is
+ # gone. merge-use-line-min: sources compiled into both a library and a
+ # unit-test binary produce slightly different function records.
+ # suspicious_hits: hot-path counters legitimately reach billions of hits
+ # over a full test run, tripping gcovr's corruption heuristic (gcc #68080).
+ $GCOVR -r . "$BUILDDIR" -j "$NPROCS" \
+ --gcov-ignore-errors=no_working_dir_found \
+ --gcov-ignore-parse-errors=suspicious_hits.warn_once_per_file \
+ --merge-mode-functions=merge-use-line-min \
+ --exclude 'lib/' --exclude '.*/unit_tests/' --exclude 'build.*/' \
+ --print-summary \
+ --html-details coverage.html \
+ --json coverage.json \
+ --xml coverage.xml
+ echo "Reports written: coverage.html coverage.json coverage.xml"
+}
-$GENHTML --output-directory coverage.html coverage.info
+configure "$@"
+build
+run_tests
+report
diff --git a/ci/rat-exclude.txt b/ci/rat-exclude.txt
index 39ad1b11ca8..8ebfa76679f 100644
--- a/ci/rat-exclude.txt
+++ b/ci/rat-exclude.txt
@@ -73,6 +73,7 @@ blib/**
**/yamlcpp/**
**/systemtap/**
**/swoc/**
+**/highway/**
tests/gold_tests/autest-site/min_cfg
tests/gold_tests/h2/rules/huge_resp_hdrs.conf
tools/http_load/**
diff --git a/cmake/ExperimentalPlugins.cmake b/cmake/ExperimentalPlugins.cmake
index 5e73ffa5ec0..4aff4543abf 100644
--- a/cmake/ExperimentalPlugins.cmake
+++ b/cmake/ExperimentalPlugins.cmake
@@ -83,7 +83,6 @@ auto_option(
)
auto_option(RATE_LIMIT FEATURE_VAR BUILD_RATE_LIMIT DEFAULT ${_DEFAULT})
auto_option(REALIP FEATURE_VAR BUILD_REALIP DEFAULT ${_DEFAULT})
-auto_option(REDO_CACHE_LOOKUP FEATURE_VAR BUILD_REDO_CACHE_LOOKUP DEFAULT ${_DEFAULT})
auto_option(SSLHEADERS FEATURE_VAR BUILD_SSLHEADERS DEFAULT ${_DEFAULT})
auto_option(STALE_RESPONSE FEATURE_VAR BUILD_STALE_RESPONSE DEFAULT ${_DEFAULT})
auto_option(
diff --git a/cmake/Findbrotli.cmake b/cmake/Findbrotli.cmake
index 8a2c63b4a80..bf12a0ab7ed 100644
--- a/cmake/Findbrotli.cmake
+++ b/cmake/Findbrotli.cmake
@@ -21,23 +21,28 @@
#
# brotli_FOUND
# brotlicommon_LIBRARY
+# brotlidec_LIBRARY
# brotlienc_LIBRARY
# brotli_INCLUDE_DIRS
#
# and the following imported targets
#
# brotli::brotlicommon
+# brotli::brotlidec
# brotli::brotlienc
#
find_library(brotlicommon_LIBRARY NAMES brotlicommon)
+find_library(brotlidec_LIBRARY NAMES brotlidec)
find_library(brotlienc_LIBRARY NAMES brotlienc)
find_path(brotli_INCLUDE_DIR NAMES brotli/encode.h)
-mark_as_advanced(brotli_FOUND brotlicommon_LIBRARY brotlienc_LIBRARY brotli_INCLUDE_DIR)
+mark_as_advanced(brotli_FOUND brotlicommon_LIBRARY brotlidec_LIBRARY brotlienc_LIBRARY brotli_INCLUDE_DIR)
include(FindPackageHandleStandardArgs)
-find_package_handle_standard_args(brotli REQUIRED_VARS brotlicommon_LIBRARY brotlienc_LIBRARY brotli_INCLUDE_DIR)
+find_package_handle_standard_args(
+ brotli REQUIRED_VARS brotlicommon_LIBRARY brotlidec_LIBRARY brotlienc_LIBRARY brotli_INCLUDE_DIR
+)
if(brotli_FOUND)
set(brotli_INCLUDE_DIRS "${brotli_INCLUDE_DIR}")
@@ -49,6 +54,12 @@ if(brotli_FOUND AND NOT TARGET brotli::brotlicommon)
target_link_libraries(brotli::brotlicommon INTERFACE "${brotlicommon_LIBRARY}")
endif()
+if(brotli_FOUND AND NOT TARGET brotli::brotlidec)
+ add_library(brotli::brotlidec INTERFACE IMPORTED)
+ target_include_directories(brotli::brotlidec INTERFACE ${brotli_INCLUDE_DIRS})
+ target_link_libraries(brotli::brotlidec INTERFACE brotli::brotlicommon "${brotlidec_LIBRARY}")
+endif()
+
if(brotli_FOUND AND NOT TARGET brotli::brotlienc)
add_library(brotli::brotlienc INTERFACE IMPORTED)
target_include_directories(brotli::brotlienc INTERFACE ${brotli_INCLUDE_DIRS})
diff --git a/configs/body_factory/default/access#redirect_url b/configs/body_factory/default/access#redirect_url
deleted file mode 100644
index 3a4fcf11e9c..00000000000
--- a/configs/body_factory/default/access#redirect_url
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
- Authentication Failed
-
-
-
-
- Authentication Failed.
-
-
- Please wait while you are redirected to another page.
- If your browser fails to redirect, click on this link.
-
-
-
diff --git a/contrib/docker/ubuntu/noble/Dockerfile b/contrib/docker/ubuntu/noble/Dockerfile
index 1270a71d6a5..d5c90227dfa 100644
--- a/contrib/docker/ubuntu/noble/Dockerfile
+++ b/contrib/docker/ubuntu/noble/Dockerfile
@@ -14,11 +14,11 @@
# the License.
#
#######################
-FROM ubuntu:noble AS build
+FROM ubuntu:noble AS build-setup
ARG LLVM_VERSION=18
ARG BASE=/opt
-ARG ATS_VERSION=10.1.0
+ARG GO_VERSION=1.26.2
RUN apt update \
&& apt upgrade --yes \
@@ -39,6 +39,7 @@ RUN apt update \
libtool \
make \
pkg-config \
+ libpsl-dev \
# ATS deps
libxml2-dev \
libjemalloc-dev \
@@ -61,12 +62,18 @@ RUN apt update \
libssl-dev \
&& apt clean --yes
+# Set up cc (and optionally c++) to use clang-18 via alternatives
+RUN update-alternatives --install /usr/bin/cc cc /usr/bin/clang-${LLVM_VERSION} 100 \
+ && update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-${LLVM_VERSION} 100 \
+ && update-alternatives --install /usr/bin/clang clang /usr/bin/clang-${LLVM_VERSION} 100 \
+ && update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-${LLVM_VERSION} 100
+
RUN rustup default stable
RUN mkdir -p ${BASE} && chmod a+rX ${BASE}
RUN if [ `uname -m` = "arm64" -o `uname -m` = "aarch64" ]; then echo "arm64" > /arch; else echo "amd64" > /arch; fi \
- && wget -qO- https://go.dev/dl/go1.21.6.linux-$(cat /arch).tar.gz | tar -C ${BASE} -xzf -
+ && wget -qO- https://go.dev/dl/go${GO_VERSION}.linux-$(cat /arch).tar.gz | tar -C ${BASE} -xzf -
ENV CC=clang-${LLVM_VERSION}
ENV CXX=clang++-${LLVM_VERSION}
@@ -82,7 +89,6 @@ RUN git clone https://boringssl.googlesource.com/boringssl \
-DCMAKE_INSTALL_PREFIX=${BASE}/boringssl \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_FLAGS='-Wno-error=ignored-attributes -UBORINGSSL_HAVE_LIBUNWIND' \
- -DCMAKE_C_FLAGS=${BSSL_C_FLAGS} \
-DBUILD_SHARED_LIBS=1 \
&& cmake \
-B build-static \
@@ -102,7 +108,7 @@ RUN git clone https://boringssl.googlesource.com/boringssl \
ENV QUICHE_BASE="${BASE}/quiche"
-RUN git clone -b 0.22.0 --depth 1 https://github.com/cloudflare/quiche.git \
+RUN git clone -b 0.28.0 --depth 1 https://github.com/cloudflare/quiche.git \
&& cd quiche \
&& QUICHE_BSSL_PATH=${BASE}/boringssl/lib QUICHE_BSSL_LINK_KIND=dylib \
cargo build -j$(nproc) --package quiche --release --features ffi,pkg-config-meta,qlog \
@@ -110,7 +116,7 @@ RUN git clone -b 0.22.0 --depth 1 https://github.com/cloudflare/quiche.git \
&& mkdir -p ${QUICHE_BASE}/include \
&& cp target/release/libquiche.a ${QUICHE_BASE}/lib/ \
&& cp target/release/libquiche.so ${QUICHE_BASE}/lib/ \
- && ln -s ${QUICHE_BASE}/lib/libquiche.so ${QUICHE_BASE}/lib/libquiche.so.0 \
+ && ln -sf ${QUICHE_BASE}/lib/libquiche.so ${QUICHE_BASE}/lib/libquiche.so.0 \
&& cp quiche/include/quiche.h ${QUICHE_BASE}/include/ \
&& cp target/release/quiche.pc ${QUICHE_BASE}/lib/pkgconfig \
&& cd .. \
@@ -121,12 +127,13 @@ ENV CFLAGS="-O3"
ENV CXXFLAGS="-O3"
ENV PKG_CONFIG_PATH="${BASE}/lib/pkgconfig:${BASE}/boringssl/lib/pkgconfig:${BASE}/quiche/lib/pkgconfig"
-RUN git clone --depth 1 -b v1.2.0 https://github.com/ngtcp2/nghttp3.git \
+RUN git clone --depth 1 -b v1.15.0 https://github.com/ngtcp2/nghttp3.git \
&& cd nghttp3 \
&& git submodule update --init \
&& autoreconf -if \
&& ./configure \
--prefix=${BASE} \
+ PKG_CONFIG_PATH=${BASE}/lib/pkgconfig:${BASE}/boringssl/lib/pkgconfig \
CFLAGS="${CFLAGS}" \
CXXFLAGS="${CXXFLAGS}" \
LDFLAGS="${LDFLAGS}" \
@@ -137,7 +144,7 @@ RUN git clone --depth 1 -b v1.2.0 https://github.com/ngtcp2/nghttp3.git \
&& rm -rf nghttp3
-RUN git clone --depth 1 -b v1.4.0 https://github.com/ngtcp2/ngtcp2.git \
+RUN git clone --depth 1 -b v1.22.1 https://github.com/ngtcp2/ngtcp2.git \
&& cd ngtcp2 \
&& autoreconf -if \
&& ./configure \
@@ -154,7 +161,7 @@ RUN git clone --depth 1 -b v1.4.0 https://github.com/ngtcp2/ngtcp2.git \
&& cd .. \
&& rm -rf ngtcp2
-RUN git clone --depth 1 -b v1.60.0 https://github.com/tatsuhiro-t/nghttp2.git \
+RUN git clone --depth 1 -b v1.69.0 https://github.com/tatsuhiro-t/nghttp2.git \
&& cd nghttp2 \
&& git submodule update --init \
&& autoreconf -if \
@@ -172,7 +179,7 @@ RUN git clone --depth 1 -b v1.60.0 https://github.com/tatsuhiro-t/nghttp2.git \
&& cd .. \
&& rm -rf nghttp2
-RUN git clone --depth 1 -b curl-8_7_1 https://github.com/curl/curl.git \
+RUN git clone --depth 1 -b curl-8_20_0 https://github.com/curl/curl.git \
&& cd curl \
&& autoreconf -fi \
&& ./configure \
@@ -189,6 +196,10 @@ RUN git clone --depth 1 -b curl-8_7_1 https://github.com/curl/curl.git \
&& cd .. \
&& rm -rf curl
+FROM build-setup as build
+
+ARG ATS_VERSION=10.1.0
+
RUN git clone --depth 1 -b ${ATS_VERSION} https://github.com/apache/trafficserver.git \
&& cmake \
-Strafficserver \
diff --git a/doc/admin-guide/configuration/proxy-protocol.en.rst b/doc/admin-guide/configuration/proxy-protocol.en.rst
index 4263d9e7717..a2cdfcd6f39 100644
--- a/doc/admin-guide/configuration/proxy-protocol.en.rst
+++ b/doc/admin-guide/configuration/proxy-protocol.en.rst
@@ -17,6 +17,8 @@
.. include:: ../../common.defs
+.. default-domain:: cpp
+
.. _proxy-protocol:
Proxy Protocol
@@ -47,10 +49,13 @@ configured with :ts:cv:`proxy.config.http.proxy_protocol_allowlist`.
.. important::
- If the allowlist is configured, requests will only be accepted from these
- IP addresses for all ports designated for Proxy Protocol in the
- :ts:cv:`proxy.config.http.server_ports` configuration, regardless of whether
- the connections have the Proxy Protocol header.
+ If the allowlist is configured, connections that begin with a Proxy
+ Protocol header preface will only be accepted from these IP addresses on
+ ports designated for Proxy Protocol in the
+ :ts:cv:`proxy.config.http.server_ports` configuration. Connections
+ without a Proxy Protocol header preface are not restricted by this
+ allowlist; use :file:`ip_allow.yaml` for general source-IP access
+ control.
By default, |TS| uses client's IP address that is from the peer when it applies ACL. If you configure a port to
enable PROXY protocol and want to apply ACL against the IP address delivered by PROXY protocol, you need to have ``PROXY`` in
@@ -59,6 +64,27 @@ enable PROXY protocol and want to apply ACL against the IP address delivered by
If you specify the server_ports flag `pp-clnt` then the client IP address used for the
transaction will be the one provided by proxy protocol.
+The ``pp-clnt`` flag governs whether the operator-visible "client IP" is the
+PROXY-Protocol source address rather than the immediate TCP peer for the
+following surfaces:
+
+* The squid log field ``%`` (and its derivatives ``%`` /
+ ``%``).
+* :func:`TSHttpTxnClientAddrGet`, :func:`TSHttpSsnClientAddrGet`, and
+ :func:`TSNetVConnClientAddrGet` (the plugin-visible client address).
+* SNI ACL evaluation, the HTTP/2 ``PEER`` ACL, SSL diagnostics, and
+ client-certificate validation.
+* Outbound transparency: when binding the proxy's outbound socket to the
+ client's address (``addr_binding = FOREIGN_ADDR``), |TS| binds to the
+ PROXY-Protocol source address only when ``pp-clnt`` is in effect.
+* HostDB parent-selection affinity: the consistent-hash key that groups
+ requests onto the same upstream peer is the PROXY-Protocol source only
+ when ``pp-clnt`` is in effect; otherwise it is the immediate TCP peer.
+
+The new log field ``%`` (and its derivatives ``%`` /
+``%``) always reports the immediate TCP peer regardless of
+``pp-clnt``.
+
1. HTTP Forwarded Header
The client IP address in the PROXY protocol header is passed to the origin server via an HTTP `Forwarded:
@@ -67,6 +93,13 @@ Detection of the PROXY protocol header is automatic. If the PROXY header
precludes the request, it will automatically be parse and made available to the
Forwarded: request header sent to the origin server.
+The legacy outbound headers ``Client-ip:``
+(:ts:cv:`proxy.config.http.insert_client_ip`) and ``X-Forwarded-For:``
+(:ts:cv:`proxy.config.http.insert_squid_x_forwarded_for`) likewise carry
+the PROXY-Protocol source address whenever a PROXY-Protocol header is
+present, mirroring the ``Forwarded: for=`` parameter. This behavior is
+independent of the ``pp-clnt`` listener flag.
+
2. Outbound PROXY protocol
See :ts:cv:`proxy.config.http.proxy_protocol_out` for configuration information.
diff --git a/doc/admin-guide/files/logging.yaml.en.rst b/doc/admin-guide/files/logging.yaml.en.rst
index 6bc7d222d36..9801a8b9218 100644
--- a/doc/admin-guide/files/logging.yaml.en.rst
+++ b/doc/admin-guide/files/logging.yaml.en.rst
@@ -258,6 +258,70 @@ supported at this time.
expect. If, for example, we had 2 accept log filters, each disjoint from the other,
nothing will ever get logged on the given log object.
+Wiping Query Parameter Values
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``wipe_field_value`` action masks the values of matching query string
+parameters before the event is written to the log. Unlike ``accept`` and
+``reject``, a ``wipe_field_value`` filter never drops an event; it only rewrites
+the logged field. This is useful for keeping secrets such as passwords, session
+tokens, or email addresses out of the access log while still logging the request.
+
+The filter examines the query string of the field named in the ``condition``
+(typically ``cquuc``, the client request URL) and, for every query parameter
+whose **name** matches one of the filter values, replaces that parameter's value
+with a run of ``X`` characters of the same length. The ``condition`` operator and
+values behave exactly as described above; use ``CASE_INSENSITIVE_CONTAIN`` (or
+``CONTAIN``) so that any parameter name containing one of the listed tokens is
+wiped.
+
+.. important::
+
+ Only the query parameter **names** are matched, never their values. A
+ parameter is wiped only when the pattern appears in the part of the parameter
+ before its ``=``. A value that happens to equal one of the filter tokens is
+ left untouched.
+
+The following filter wipes the values of a set of sensitive parameters:
+
+.. code:: yaml
+
+ filters:
+ - name: queryparamescaper_cquuc
+ action: WIPE_FIELD_VALUE
+ condition: cquuc CASE_INSENSITIVE_CONTAIN password,secret,access_token,session_redirect,cardNumber,code,query,search-query,prefix,keywords,email,handle
+
+Given that filter attached to a log using the ``%`` format, the following
+request URLs are logged as shown. Note that ``cquuc`` is the client request's
+*canonical* URL, so the logged value includes the scheme and host:
+
+.. list-table::
+ :header-rows: 1
+
+ * - Requested URL
+ - Logged value
+ * - ``http://example.com/test-1?name=value&email=123@gmail.com``
+ - ``http://example.com/test-1?name=value&email=XXXXXXXXXXXXX``
+ * - ``http://example.com/test-2?email=123@gmail.com&name=password``
+ - ``http://example.com/test-2?email=XXXXXXXXXXXXX&name=password``
+ * - ``http://example.com/test-3?trivial=password&name1=val1&email=123@gmail.com``
+ - ``http://example.com/test-3?trivial=password&name1=val1&email=XXXXXXXXXXXXX``
+ * - ``http://example.com/test-4?trivial=password&email=&name=handle&session_redirect=wiped_string``
+ - ``http://example.com/test-4?trivial=password&email=&name=handle&session_redirect=XXXXXXXXXXXX``
+ * - ``http://example.com/test-5?trivial=password&email=123@gmail.com&email=456@gmail.com&session_redirect=wiped_string&email=789@gmail.com&name=value``
+ - ``http://example.com/test-5?trivial=password&email=XXXXXXXXXXXXX&email=XXXXXXXXXXXXX&session_redirect=XXXXXXXXXXXX&email=XXXXXXXXXXXXX&name=value``
+
+Note the behavior demonstrated above:
+
+- In ``test-2`` the ``name=password`` parameter is **not** wiped: the token
+ ``password`` appears in the value, not in the parameter name.
+- In ``test-3`` the ``trivial=password`` parameter is likewise left alone for the
+ same reason, while ``email`` is wiped.
+- An empty value (``email=`` in ``test-4``) matches but produces an empty wipe,
+ since there is nothing to mask.
+- In ``test-5`` every occurrence of the repeated ``email`` parameter is wiped, not
+ just the first.
+
.. _admin-custom-logs-logs:
@@ -289,6 +353,15 @@ filename string The name of the logfile relative to the defau
format string a string with a valid named format specification.
header string If present, emitted as the first line of each
new log file.
+binary_log_version number For ``binary`` logs only: the on-disk segment
+ format version, ``2`` or ``3`` (default
+ ``3``). Version 3 is self-describing (embeds a
+ per-field type schema so a generic reader can
+ decode the file without an ATS symbol table);
+ use ``2`` to emit the legacy layout for
+ downstream parsers that do not yet understand
+ version 3. Ignored for ``ascii`` and
+ ``ascii_pipe``.
rolling_enabled *see below* Determines the type of log rolling to use (or
whether to disable rolling). Overrides
:ts:cv:`proxy.config.log.rolling_enabled`.
@@ -389,3 +462,28 @@ matched the REFRESH_HIT filter we created.
format: summaryfmt
filters:
- refreshhitfilter
+
+The following is an example of a binary log. Binary logs are written in the
+self-describing version 3 format by default, so ``traffic_logcat`` and
+``traffic_logstats`` (and any reader built from the
+:ref:`v3 format specification `) can decode the
+``minimal.blog`` file without an embedded copy of the field table:
+
+.. code:: yaml
+
+ logs:
+ - mode: binary
+ filename: minimal
+ format: minimalfmt
+
+To keep emitting the older version 2 layout for a downstream parser that does
+not yet understand version 3, pin the object with ``binary_log_version``. This
+key only applies to ``binary`` logs and defaults to ``3``:
+
+.. code:: yaml
+
+ logs:
+ - mode: binary
+ filename: minimal_legacy
+ format: minimalfmt
+ binary_log_version: 2
diff --git a/doc/admin-guide/files/records.yaml.en.rst b/doc/admin-guide/files/records.yaml.en.rst
index 6fdc5242750..a554c554a2d 100644
--- a/doc/admin-guide/files/records.yaml.en.rst
+++ b/doc/admin-guide/files/records.yaml.en.rst
@@ -17,6 +17,8 @@
.. include:: ../../common.defs
+.. default-domain:: cpp
+
.. configfile:: records.yaml
records.yaml
@@ -462,7 +464,7 @@ Thread Variables
before :program:`traffic_server` drops privilege. If this variable
is set to ``NULL``, no helper will be spawned.
-.. ts::vc:: CONFIG proxy.config.core_limit INT -1
+.. ts:cv:: CONFIG proxy.config.core_limit INT -1
This option specifies the size limit for core files in the event
that :program:`traffic_server` crashes. ``-1`` means there is
@@ -750,11 +752,6 @@ Management
* Set the ``user_id=#-1`` and start trafficserver as root.
-.. ts:cv:: CONFIG proxy.config.admin.api.restricted INT 0
-
- This is now deprecated, please refer to :ref:`admin-jsonrpc-configuration` to find
- out about the new admin API mechanism.
-
HTTP Engine
===========
@@ -844,6 +841,15 @@ pp
:ref:`Proxy Protocol ` for more details on how to configure
this option properly.
+pp-clnt
+ Use the source address from the Proxy Protocol header as the client IP
+ address for the transaction. This affects which address is reported by the
+ ``%`` log field, by the plugin client-address APIs (such as
+ :func:`TSHttpTxnClientAddrGet`), and by SNI / HTTP/2 peer ACLs and SSL
+ client-certificate validation, among other surfaces. Only meaningful in
+ combination with ``pp``. See :ref:`Proxy Protocol ` for the
+ full enumeration of behaviors gated by this flag.
+
tr-full
Fully transparent. This is a convenience option and is identical to specifying both ``tr-in`` and ``tr-out``.
@@ -1812,15 +1818,28 @@ Origin Server Connect Attempts
The maximum number of connection retries |TS| can make when the origin server is not responding.
Each retry attempt lasts for `proxy.config.http.connect_attempts_timeout`_ seconds. Once the maximum number of retries is
reached, the origin is marked down (as controlled by `proxy.config.http.connect.down.policy`_. After this, the setting
- `proxy.config.http.connect_attempts_max_retries_down_server`_ is used to limit the number of retry attempts to the known down origin.
+ `proxy.config.http.connect_attempts_max_retries_suspect_server`_ is used to limit the number of retry attempts when the origin is
+ in the SUSPECT state (recovering after `proxy.config.http.down_server.cache_time`_ has elapsed).
+
+.. ts:cv:: CONFIG proxy.config.http.connect_attempts_max_retries_suspect_server INT 1
+ :reloadable:
+ :overridable:
+
+ Maximum number of connection retries |TS| can make while an origin is in the SUSPECT state (the first request after
+ `proxy.config.http.down_server.cache_time`_ has elapsed on a previously-down origin). The total attempt budget for a SUSPECT
+ origin is therefore ``connect_attempts_max_retries_suspect_server + 1`` (the initial probe plus each retry). If any attempt
+ succeeds, the origin transitions back to UP; if all attempts fail, the origin returns to DOWN for another
+ `proxy.config.http.down_server.cache_time`_ seconds. Typically smaller than `proxy.config.http.connect_attempts_max_retries`_
+ so the recovering origin is not flooded.
.. ts:cv:: CONFIG proxy.config.http.connect_attempts_max_retries_down_server INT 1
:reloadable:
:overridable:
+ :deprecated:
- Maximum number of connection attempts |TS| can make while an origin is marked down per request. Typically this value is smaller than
- `proxy.config.http.connect_attempts_max_retries`_ so an error is returned to the client faster and also to reduce the load on the down origin.
- The timeout interval `proxy.config.http.connect_attempts_timeout`_ in seconds is used with this setting.
+ This setting is deprecated in favor of :ts:cv:`proxy.config.http.connect_attempts_max_retries_suspect_server`. If the
+ deprecated setting is set explicitly and the replacement is not, the deprecated value is mirrored forward and a warning is
+ logged. If both are set explicitly, the new setting wins and the deprecated value is ignored.
.. ts:cv:: CONFIG proxy.config.http.connect_attempts_retry_backoff_base INT 0
:reloadable:
@@ -1948,6 +1967,9 @@ Origin Server Connect Attempts
:overridable:
Specifies how long (in seconds) |TS| remembers that an origin server was unreachable.
+ During this window, if a stale cached response exists and its age is within the cached response's ``max-age`` plus
+ :ts:cv:`proxy.config.http.cache.max_stale_age`, |TS| serves the stale content directly
+ without attempting to contact the origin server.
.. ts:cv:: CONFIG proxy.config.http.uncacheable_requests_bypass_parent INT 1
:reloadable:
@@ -2168,10 +2190,13 @@ Proxy User Variables
.. ts:cv:: CONFIG proxy.config.http.proxy_protocol_allowlist STRING ``````
- This defines a allowlist of server IPs that are trusted to provide
- connections with Proxy Protocol information. This is a comma delimited list
- of IP addresses. Addressed may be listed individually, in a range separated
- by a dash or by using CIDR notation.
+ This defines an allowlist of server IPs that are trusted to provide
+ connections with Proxy Protocol information. This allowlist is enforced only
+ for connections that begin with a Proxy Protocol header preface; non-Proxy
+ Protocol traffic on flexible Proxy Protocol ports is not restricted by this
+ setting. Use :file:`ip_allow.yaml` for general source-IP access control. This
+ is a comma delimited list of IP addresses. Addresses may be listed
+ individually, in a range separated by a dash, or by using CIDR notation.
======================= ===========================================================
Example Effect
@@ -2581,7 +2606,6 @@ Cache Control
Setting this to ``0`` disables the feature.
.. ts:cv:: CONFIG proxy.config.http.cache.try_compat_key_read INT 0
- :reloadable:
When enabled (``1``), |TS| will try to lookup the cached object using the
previous cache key generation algorithm, but will always write new objects
@@ -2753,7 +2777,7 @@ Cache Control
a minimum time, and the actual sync may be delayed if the disks are larger than
how fast we allow it to write to disk (see next options).
-.. ts:cv:: CONFIG proxy.config.cache.dir.sync_max_writes INT 2097152
+.. ts:cv:: CONFIG proxy.config.cache.dir.sync_max_write INT 2097152
:units: bytes
How much of a stripes cache directory we will write to disk in each write cycle.
@@ -2881,10 +2905,65 @@ RAM Cache
.. ts:cv:: CONFIG proxy.config.cache.ram_cache.algorithm INT 1
- Two distinct RAM caches are supported, the default (1) being the simpler
- **LRU** (*Least Recently Used*) cache. As an alternative, the **CLFUS**
- (*Clocked Least Frequently Used by Size*) is also available, by changing this
- configuration to 0.
+ Three RAM cache eviction algorithms are supported, selected by this value:
+
+ ``1``
+ **LRU** (*Least Recently Used*), the default -- the simplest policy,
+ favoring recency. Pairs with
+ :ts:cv:`proxy.config.cache.ram_cache.use_seen_filter` for scan
+ resistance.
+
+ ``0``
+ **CLFUS** (*Clocked Least Frequently Used by Size*), which balances
+ recency, frequency, and object size. It is the only algorithm that
+ supports in-RAM compression
+ (:ts:cv:`proxy.config.cache.ram_cache.compress`).
+
+ ``2``
+ **S3-FIFO** (*Simple Scalable Static FIFO*): a small admission queue and
+ a main queue (both FIFO), plus a ghost queue of recently evicted keys,
+ which together filter one-hit-wonders. Scan-resistant and inexpensive
+ (no per-hit reordering); strong hit rates on CDN and key-value
+ workloads. Its eviction metadata (including the ghost) is accounted
+ within :ts:cv:`proxy.config.cache.ram_cache.size`. Experimental; it does
+ not use the seen filter or support in-RAM compression. Its queue split,
+ ghost bounds, and promotion threshold can be tuned with the
+ ``proxy.config.cache.ram_cache.s3fifo.*`` settings below; the defaults
+ follow the original paper and suit most workloads.
+
+.. ts:cv:: CONFIG proxy.config.cache.ram_cache.s3fifo.main_percent INT 90
+
+ Only applies when :ts:cv:`proxy.config.cache.ram_cache.algorithm` is ``2``
+ (S3-FIFO). The target size of the main queue as a percentage of the resident
+ budget; the remainder is the small admission queue. The default ``90`` gives
+ the ~10% small / ~90% main split from the paper. Valid range is ``1`` to
+ ``99`` (an out-of-range value is rejected with a warning and the default is
+ used); a larger value grows the main queue at the expense of the admission
+ queue.
+
+.. ts:cv:: CONFIG proxy.config.cache.ram_cache.s3fifo.ghost_size_percent INT 90
+
+ Only applies when :ts:cv:`proxy.config.cache.ram_cache.algorithm` is ``2``
+ (S3-FIFO). The ghost queue remembers the keys of recently evicted objects for
+ up to this percentage of :ts:cv:`proxy.config.cache.ram_cache.size` worth of
+ object bytes. Valid range is ``0`` to ``100``; ``0`` disables this bound.
+
+.. ts:cv:: CONFIG proxy.config.cache.ram_cache.s3fifo.ghost_mem_percent INT 25
+
+ Only applies when :ts:cv:`proxy.config.cache.ram_cache.algorithm` is ``2``
+ (S3-FIFO). Caps the memory the ghost queue's per-key metadata may consume at
+ this percentage of :ts:cv:`proxy.config.cache.ram_cache.size`. This metadata
+ is counted against the configured RAM cache size, so total memory stays
+ within the budget regardless of object cardinality. Valid range is ``0`` to
+ ``100``; ``0`` disables the ghost queue.
+
+.. ts:cv:: CONFIG proxy.config.cache.ram_cache.s3fifo.promote_threshold INT 2
+
+ Only applies when :ts:cv:`proxy.config.cache.ram_cache.algorithm` is ``2``
+ (S3-FIFO). The number of times an object in the small admission queue must be
+ reused before it is promoted to the main queue instead of being demoted to
+ the ghost. The default ``2`` admits objects seen at least twice. Valid range
+ is ``1`` to ``3``; a higher value makes admission to the main queue stricter.
.. ts:cv:: CONFIG proxy.config.cache.ram_cache.use_seen_filter INT 1
@@ -2954,13 +3033,11 @@ Dynamic Content & Content Negotiation
=====================================
.. ts:cv:: CONFIG proxy.config.http.cache.open_read_retry_time INT 10
- :reloadable:
:overridable:
The number of milliseconds a cacheable request will wait before requesting the object from cache if an equivalent request is in flight.
.. ts:cv:: CONFIG proxy.config.http.cache.max_open_read_retries INT -1
- :reloadable:
:overridable:
The number of times to attempt fetching an object from cache if there was an equivalent request in flight.
@@ -3060,7 +3137,6 @@ Customizable User Response Pages
Maximum size of the error template response page.
.. ts:cv:: CONFIG proxy.config.body_factory.response_suppression_mode INT 0
- :reloadable:
:overridable:
Specifies when |TS| suppresses generated response pages:
@@ -3587,7 +3663,7 @@ Logging Configuration
How often |TS| executes log related periodic tasks, in seconds
-.. ts:cv:: CONFIG proxy.config.log.proxy.config.log.throttling_interval_msec INT 60000
+.. ts:cv:: CONFIG proxy.config.log.throttling_interval_msec INT 60000
:reloadable:
:units: milliseconds
@@ -3765,7 +3841,7 @@ Diagnostic Logging Configuration
For details about how log throttling works, see
:ts:cv:`log.throttling_interval_msec
- `.
+ `.
.. ts:cv:: CONFIG proxy.config.diags.logfile.filename STRING diags.log
@@ -4280,6 +4356,58 @@ SSL Termination
``1`` Enables the use of Kernel TLS..
===== ======================================================================
+.. ts:cv:: CONFIG proxy.config.ssl.server.cert_compression.algorithms STRING
+ :reloadable:
+
+ A comma-separated list of compression algorithms that |TS| is willing to
+ use for TLS Certificate Compression
+ (`RFC 8879 `_) when |TS|
+ acts as a TLS server (i.e. accepting connections from clients). When a
+ connecting client advertises support for one of these algorithms, |TS| will
+ send its certificate in compressed form, reducing handshake size.
+
+ Supported values: ``zlib``, ``brotli``, ``zstd``. The order determines the
+ server's preference. An empty value (the default) disables certificate
+ compression.
+
+ ``brotli`` and ``zstd`` are only available when |TS| is compiled with the
+ corresponding libraries.
+
+ Example::
+
+ proxy.config.ssl.server.cert_compression.algorithms: zlib,brotli
+
+.. ts:cv:: CONFIG proxy.config.ssl.server.cert_compression.cache INT 1
+ :reloadable:
+
+ Controls whether the compressed certificate is reused across
+ handshakes. With caching enabled (the default), the certificate is
+ compressed once and the result is reused. With caching disabled, the
+ certificate is recompressed on every handshake.
+
+ Has no effect on OpenSSL builds; OpenSSL always reuses the
+ compressed result.
+
+ ===== =================
+ Value Description
+ ===== =================
+ ``0`` Disables caching.
+ ``1`` Enables caching.
+ ===== =================
+
+.. ts:cv:: CONFIG proxy.config.ssl.client.cert_compression.algorithms STRING
+ :reloadable:
+
+ A comma-separated list of compression algorithms that |TS| advertises for
+ TLS Certificate Compression
+ (`RFC 8879 `_) when |TS|
+ acts as a TLS client (i.e. connecting to origin servers). When the origin
+ supports one of these algorithms, |TS| will accept and decompress the
+ certificate.
+
+ Supported values: ``zlib``, ``brotli``, ``zstd``. An empty value (the
+ default) disables certificate compression.
+
Client-Related Configuration
----------------------------
@@ -4362,6 +4490,7 @@ Client-Related Configuration
.. ts:cv:: CONFIG proxy.config.ssl.client.CA.cert.path STRING NULL
:reloadable:
+ :overridable:
Specifies the location of the certificate authority file against
which the origin server will be verified.
@@ -5008,14 +5137,12 @@ removed in the future without prior notice.
A size of hash table that stores connection information.
-.. ts:cv:: CONFIG proxy.config.quic.proxy.config.quic.num_alt_connection_ids INT 65521
- :reloadable:
+.. ts:cv:: CONFIG proxy.config.quic.num_alt_connection_ids INT 8
A number of alternate Connection IDs that |TS| provides to a peer. It has to
be at least 8.
-.. ts:cv:: CONFIG proxy.config.quic.stateless_retry_enabled INT 0
- :reloadable:
+.. ts:cv:: CONFIG proxy.config.quic.server.stateless_retry_enabled INT 0
Enables Stateless Retry.
@@ -5030,19 +5157,16 @@ removed in the future without prior notice.
Enables connection migration exercise on origin server connections.
.. ts:cv:: CONFIG proxy.config.quic.server.supported_groups STRING "P-256:X25519:P-384:P-521"
- :reloadable:
Configures the list of supported groups provided by OpenSSL which will be
used to determine the set of shared groups on QUIC origin server connections.
.. ts:cv:: CONFIG proxy.config.quic.client.supported_groups STRING "P-256:X25519:P-384:P-521"
- :reloadable:
Configures the list of supported groups provided by OpenSSL which will be
used to determine the set of shared groups on QUIC client connections.
.. ts:cv:: CONFIG proxy.config.quic.client.session_file STRING ""
- :reloadable:
Only available for :program:`traffic_quic`.
If specified, TLS session data will be stored to the file, and will be used
@@ -5092,61 +5216,61 @@ removed in the future without prior notice.
This value will be advertised as ``initial_max_data`` Transport Parameter.
-.. ts:cv:: CONFIG proxy.config.quic.max_stream_data_bidi_local_in INT 0
+.. ts:cv:: CONFIG proxy.config.quic.initial_max_stream_data_bidi_local_in INT 0
:reloadable:
This value will be advertised as ``initial_max_stream_data_bidi_local``
Transport Parameter.
-.. ts:cv:: CONFIG proxy.config.quic.max_stream_data_bidi_local_out INT 4096
+.. ts:cv:: CONFIG proxy.config.quic.initial_max_stream_data_bidi_local_out INT 4096
:reloadable:
This value will be advertised as ``initial_max_stream_data_bidi_local``
Transport Parameter.
-.. ts:cv:: CONFIG proxy.config.quic.max_stream_data_bidi_remote_in INT 4096
+.. ts:cv:: CONFIG proxy.config.quic.initial_max_stream_data_bidi_remote_in INT 4096
:reloadable:
This value will be advertised as ``initial_max_stream_data_bidi_remote``
Transport Parameter.
-.. ts:cv:: CONFIG proxy.config.quic.max_stream_data_bidi_remote_out INT 0
+.. ts:cv:: CONFIG proxy.config.quic.initial_max_stream_data_bidi_remote_out INT 0
:reloadable:
This value will be advertised as ``initial_max_stream_data_bidi_remote``
Transport Parameter.
-.. ts:cv:: CONFIG proxy.config.quic.max_stream_data_uni_in INT 4096
+.. ts:cv:: CONFIG proxy.config.quic.initial_max_stream_data_uni_in INT 4096
:reloadable:
This value will be advertised as ``initial_max_stream_data_uni``
Transport Parameter.
-.. ts:cv:: CONFIG proxy.config.quic.max_stream_data_uni_out INT 0
+.. ts:cv:: CONFIG proxy.config.quic.initial_max_stream_data_uni_out INT 4096
:reloadable:
This value will be advertised as ``initial_max_stream_data_uni``
Transport Parameter.
-.. ts:cv:: CONFIG proxy.config.quic.max_streams_bidi_in INT 100
+.. ts:cv:: CONFIG proxy.config.quic.initial_max_streams_bidi_in INT 100
:reloadable:
This value will be advertised as ``initial_max_streams_bidi``
Transport Parameter.
-.. ts:cv:: CONFIG proxy.config.quic.max_streams_bidi_out INT 100
+.. ts:cv:: CONFIG proxy.config.quic.initial_max_streams_bidi_out INT 100
:reloadable:
This value will be advertised as ``initial_max_streams_bidi``
Transport Parameter.
-.. ts:cv:: CONFIG proxy.config.quic.max_streams_uni_in INT 100
+.. ts:cv:: CONFIG proxy.config.quic.initial_max_streams_uni_in INT 100
:reloadable:
This value will be advertised as ``initial_max_streams_uni``
Transport Parameter.
-.. ts:cv:: CONFIG proxy.config.quic.max_streams_uni_out INT 100
+.. ts:cv:: CONFIG proxy.config.quic.initial_max_streams_uni_out INT 100
:reloadable:
This value will be advertised as ``initial_max_streams_uni``
@@ -5380,7 +5504,6 @@ Sockets
Note: If MPTCP is enabled, TCP_DEFER_ACCEPT is only supported on Linux kernels 5.19+.
.. ts:cv:: CONFIG proxy.config.net.listen_backlog INT -1
- :reloadable:
This directive sets the maximum number of pending connections.
If it is set to -1, |TS| will automatically set this
diff --git a/doc/admin-guide/logging/formatting.en.rst b/doc/admin-guide/logging/formatting.en.rst
index 48ce9402a87..420df43090b 100644
--- a/doc/admin-guide/logging/formatting.en.rst
+++ b/doc/admin-guide/logging/formatting.en.rst
@@ -151,6 +151,7 @@ Cache Details
.. _crc:
.. _crsc:
.. _chm:
+.. _ckh:
.. _cwr:
.. _cwtr:
.. _crra:
@@ -166,6 +167,10 @@ Field Source Description
cluc Client Request Cache Lookup URL, also known as the :term:`cache key`,
which is the canonicalized version of the client request
URL.
+ckh Proxy Cache Cache Key Hash. The base64-encoded cryptographic hash of the
+ effective cache key used for cache lookup and storage. This
+ is the actual key used to index cache objects. Empty
+ (``-``) when no cache lookup was performed.
crc Proxy Cache Cache Result Code. The result of |TS| attempting to obtain
the object from cache; :ref:`admin-logging-cache-results`.
crsc Proxy Cache Cache Result Sub-Code. More specific code to complement the
@@ -472,6 +477,7 @@ Lengths and Sizes
.. _cqcl:
.. _cqhl:
.. _cqql:
+.. _cqqtl:
.. _csscl:
.. _csshl:
.. _cssql:
@@ -482,6 +488,7 @@ Lengths and Sizes
.. _pscl:
.. _pshl:
.. _psql:
+.. _psqtl:
.. _sscl:
.. _sshl:
.. _ssql:
@@ -497,6 +504,10 @@ cqcl Client Request Client request content length, in bytes.
cqhl Client Request Client request header length, in bytes.
cqql Client Request Client request header and content length combined,
in bytes.
+cqqtl Client Request Same as cqql_, but for the first transaction on a
+ TLS connection, also includes TLS handshake bytes
+ received from the client. Note that this metric
+ may not always be 100% accurate.
csscl Cached Origin Response Content body length from cached origin response.
csshl Cached Origin Response Header length from cached origin response.
cssql Cached Origin Response Content and header length from cached origin
@@ -512,6 +523,10 @@ pscl Proxy Response Content body length of the |TS| proxy response.
pshl Proxy Response Header length of the |TS| response to client.
psql Proxy Response Content body and header length combined of the
|TS| response to client.
+psqtl Proxy Response Same as psql_, but for the first transaction on a
+ TLS connection, also includes TLS handshake bytes
+ sent to the client. Note that this metric may not
+ always be 100% accurate.
sscl Origin Response Content body length of the origin server response
to |TS|.
sshl Origin Response Header length of the origin server response.
@@ -672,10 +687,14 @@ SSL / Encryption
.. _cscert:
.. _cqssl:
.. _cqssr:
+.. _cqssrt:
.. _cqssv:
.. _cqssc:
.. _cqssu:
.. _cqssa:
+.. _cthbr:
+.. _cthbt:
+.. _cthb:
.. _pqssl:
.. _pscert:
@@ -693,9 +712,15 @@ cscert Client Request 1 if |TS| requested certificate from client during TLS
handshake. 0 otherwise.
cqssl Client Request SSL client request status indicates if this client
connection is over SSL.
-cqssr Client Request SSL session ticket reused status; indicates if the current
- request hit the SSL session ticket and avoided a full SSL
- handshake.
+cqssr Client Request SSL session resumption status; indicates whether the
+ current request was resumed from a previous SSL session
+ and avoided a full TLS handshake. Resumption may have
+ been via a server side session cache or via a TLS session
+ ticket, see cqssrt_ for the resumption type.
+cqssrt Client Request SSL resumption type; indicates the type of TLS session
+ resumption used for this request. 0 for no resumption,
+ 1 for server session cache resumption, 2 for TLS session
+ ticket resumption.
cqssv Client Request SSL version used to communicate with the client.
cqssc Client Request SSL Cipher used by |TS| to communicate with the client.
cqssu Client Request SSL Elliptic Curve used by |TS| to communicate with the
@@ -706,6 +731,17 @@ cqssg Client Request SSL Group used by |TS| to communicate with the client.
OpenSSL 3.2 or later or a version of BoringSSL that
supports querying group names.
cqssa Client Request ALPN Protocol ID negotiated with the client.
+cthbr Client Request TLS handshake bytes received from the client. This is the
+ number of bytes read from the client during the TLS
+ handshake. Populated for all transactions on a TLS connection,
+ including reused connections.
+cthbt Client Request TLS handshake bytes sent to the client. This is the number
+ of bytes written to the client during the TLS handshake.
+ Populated for all transactions on a TLS connection,
+ including reused connections.
+cthb Client Request Total TLS handshake bytes (received + sent). This is the
+ sum of cthbr_ and cthbt_. Populated for all transactions
+ on a TLS connection, including reused connections.
pqssl Proxy Request Indicates whether the connection from |TS| to the origin
was over SSL or not.
pqssr Proxy Request SSL session ticket reused status from |TS| to the origin;
diff --git a/doc/admin-guide/monitoring/statistics/core/general.en.rst b/doc/admin-guide/monitoring/statistics/core/general.en.rst
index 23247760cc5..6479082310d 100644
--- a/doc/admin-guide/monitoring/statistics/core/general.en.rst
+++ b/doc/admin-guide/monitoring/statistics/core/general.en.rst
@@ -81,5 +81,12 @@ General
.. ts:stat:: global proxy.process.traffic_server.memory.rss integer
:units: bytes
- The resident set size (RSS) of the ``traffic_server`` process. This is
- basically the amount of memory this process is consuming.
+ The current resident set size (RSS) of the ``traffic_server`` process. This
+ is basically the amount of memory this process is consuming. The value is
+ refreshed every 10 seconds and reflects current (not peak) usage, so it can
+ both rise and fall over the lifetime of the process.
+
+ This gauge is only published when the memory-limit feature is enabled (that
+ is, when :ts:cv:`proxy.config.memory.max_usage` is greater than 0); when the
+ feature is disabled the process does not sample RSS and the metric is not
+ reported.
diff --git a/doc/admin-guide/monitoring/statistics/core/network-io.en.rst b/doc/admin-guide/monitoring/statistics/core/network-io.en.rst
index 56168ab9595..f26d51457cc 100644
--- a/doc/admin-guide/monitoring/statistics/core/network-io.en.rst
+++ b/doc/admin-guide/monitoring/statistics/core/network-io.en.rst
@@ -79,10 +79,29 @@ Network I/O
:type: counter
:units: bytes
+ Application-layer bytes read from client and origin connections. For TLS
+ connections this is the decrypted payload, symmetric with ``write_bytes``; it
+ does not include TLS handshake or record-layer framing.
+
+.. ts:stat:: global proxy.process.net.read_bytes_count integer
+ :type: counter
+
+ The number of read operations that contributed to ``read_bytes``. For TLS
+ connections this is one per decrypted-read pass, not per socket read.
+
.. ts:stat:: global proxy.process.net.write_bytes integer
:type: counter
:units: bytes
+ Application-layer bytes written to client and origin connections. For TLS
+ connections this is the plaintext payload; it does not include TLS handshake
+ or record-layer framing.
+
+.. ts:stat:: global proxy.process.net.write_bytes_count integer
+ :type: counter
+
+ The number of write operations that contributed to ``write_bytes``.
+
.. ts:stat:: global proxy.process.tcp.total_accepts integer
:type: counter
diff --git a/doc/admin-guide/monitoring/statistics/core/ssl.en.rst b/doc/admin-guide/monitoring/statistics/core/ssl.en.rst
index efef309c222..c3dc1bb7994 100644
--- a/doc/admin-guide/monitoring/statistics/core/ssl.en.rst
+++ b/doc/admin-guide/monitoring/statistics/core/ssl.en.rst
@@ -389,3 +389,69 @@ Stats for Pre-warming TLS Tunnel is registered dynamically. The ``POOL`` in belo
:type: counter
Represents the total number of pre-warming retry.
+
+.. ts:stat:: global proxy.process.ssl.cert_compress.zlib integer
+ :type: counter
+
+ The number of times a server certificate was compressed with zlib during a
+ TLS handshake.
+
+.. ts:stat:: global proxy.process.ssl.cert_compress.zlib_failure integer
+ :type: counter
+
+ The number of times zlib compression of a server certificate failed.
+
+.. ts:stat:: global proxy.process.ssl.cert_decompress.zlib integer
+ :type: counter
+
+ The number of times a certificate received from an origin server was
+ decompressed with zlib.
+
+.. ts:stat:: global proxy.process.ssl.cert_decompress.zlib_failure integer
+ :type: counter
+
+ The number of times zlib decompression of a certificate failed.
+
+.. ts:stat:: global proxy.process.ssl.cert_compress.brotli integer
+ :type: counter
+
+ The number of times a server certificate was compressed with Brotli during a
+ TLS handshake.
+
+.. ts:stat:: global proxy.process.ssl.cert_compress.brotli_failure integer
+ :type: counter
+
+ The number of times Brotli compression of a server certificate failed.
+
+.. ts:stat:: global proxy.process.ssl.cert_decompress.brotli integer
+ :type: counter
+
+ The number of times a certificate received from an origin server was
+ decompressed with Brotli.
+
+.. ts:stat:: global proxy.process.ssl.cert_decompress.brotli_failure integer
+ :type: counter
+
+ The number of times Brotli decompression of a certificate failed.
+
+.. ts:stat:: global proxy.process.ssl.cert_compress.zstd integer
+ :type: counter
+
+ The number of times a server certificate was compressed with zstd during a
+ TLS handshake.
+
+.. ts:stat:: global proxy.process.ssl.cert_compress.zstd_failure integer
+ :type: counter
+
+ The number of times zstd compression of a server certificate failed.
+
+.. ts:stat:: global proxy.process.ssl.cert_decompress.zstd integer
+ :type: counter
+
+ The number of times a certificate received from an origin server was
+ decompressed with zstd.
+
+.. ts:stat:: global proxy.process.ssl.cert_decompress.zstd_failure integer
+ :type: counter
+
+ The number of times zstd decompression of a certificate failed.
diff --git a/doc/admin-guide/plugins/maxmind_acl.en.rst b/doc/admin-guide/plugins/maxmind_acl.en.rst
index d0a4aacb97a..32fc35d199e 100644
--- a/doc/admin-guide/plugins/maxmind_acl.en.rst
+++ b/doc/admin-guide/plugins/maxmind_acl.en.rst
@@ -113,4 +113,49 @@ The plugin also supports optional fields from GeoGuard databases which includes:
``vpn_datacenter``
``relay_proxy``
``proxy_over_vpn``
-``smart_dns_proxy``
\ No newline at end of file
+``smart_dns_proxy``
+
+Bypass
+======
+
+An optional ``bypass`` field allows a request to skip all geo checks entirely and pass through
+unmodified. Both a header name and an expected value must be configured; when the named header
+is present in the request **and** its value matches exactly, the plugin returns immediately
+without performing any country, IP, regex, or anonymous evaluation.
+
+``header``
+ Required sub-key. The name of the HTTP request header to look for, e.g. ``@GeoBypass``.
+
+``value``
+ Required sub-key. The header field value must match this string exactly for the bypass to
+ trigger. Both ``header`` and ``value`` must be present and non-empty; omitting either
+ disables the bypass entirely and a warning is emitted to the ATS error log.
+
+The comparison uses the complete, raw field value of the first occurrence of the named header.
+Duplicate headers with the same name (repeated lines) are ignored — only the first is evaluated.
+Within that first field, the entire value must match exactly, so a comma-separated multi-value
+(e.g. ``@GeoBypass: 1, extra``) in a single header line will not match a simple configured value.
+
+An example configuration ::
+
+ maxmind:
+ database: GeoIP2-City.mmdb
+ bypass:
+ header: "@GeoBypass"
+ value: "1"
+ allow:
+ country:
+ - US
+
+This is useful for internal or trusted upstream services that should not be subject to geo
+restrictions. If ``bypass`` is absent from the configuration, or if either ``header`` or
+``value`` is missing, bypass is disabled and all requests are evaluated normally.
+
+.. warning::
+
+ Because the bypass skips **all** ACL checks, the configured header must be
+ unforgeable by external clients. Use an internal ``@``-prefixed header (e.g.
+ ``@GeoBypass``) that is set by ATS itself or a trusted upstream, or
+ ensure the edge strips/overwrites the header before it reaches this plugin.
+ Configuring a normal client-supplied header allows end users to opt out of
+ geo restrictions by simply sending the header in their request.
\ No newline at end of file
diff --git a/doc/admin-guide/storage/index.en.rst b/doc/admin-guide/storage/index.en.rst
index f68808b1016..aa3754df5eb 100644
--- a/doc/admin-guide/storage/index.en.rst
+++ b/doc/admin-guide/storage/index.en.rst
@@ -75,18 +75,22 @@ and reduces load on disks, especially during temporary traffic peaks.
You can configure the RAM cache size to suit your needs, as described in
:ref:`changing-the-size-of-the-ram-cache` below.
-The RAM cache supports two cache eviction algorithms, a regular *LRU*
-(Least Recently Used) and the more advanced *CLFUS* (Clocked Least
+The RAM cache supports three cache eviction algorithms: a regular *LRU*
+(Least Recently Used); the more advanced *CLFUS* (Clocked Least
Frequently Used by Size; which balances recentness, frequency, and size
-to maximize hit rate, similar to a most frequently used algorithm).
-The default is to use *LRU*, and this is controlled via
+to maximize hit rate, similar to a most frequently used algorithm); and
+*S3-FIFO* (Simple Scalable Static FIFO), a FIFO-based policy whose small
+admission queue and ghost list filter one-hit-wonders, giving strong hit
+rates on CDN and key-value workloads at low cost. The default is to use
+*LRU*, and this is controlled via
:ts:cv:`proxy.config.cache.ram_cache.algorithm`.
Both the *LRU* and *CLFUS* RAM caches support a configuration to increase
scan resistance. In a typical *LRU*, if you request all possible objects in
sequence, you will effectively churn the cache on every request. The option
:ts:cv:`proxy.config.cache.ram_cache.use_seen_filter` can be set to add some
-resistance against this problem.
+resistance against this problem. *S3-FIFO* is scan-resistant by design,
+through its admission queue, and does not use the seen filter.
In addition, *CLFUS* also supports compressing in the RAM cache itself.
This can be useful for content which is not compressed by itself (e.g.
diff --git a/doc/appendices/command-line/traffic_ctl.en.rst b/doc/appendices/command-line/traffic_ctl.en.rst
index dc560fe9cc7..5fff291cf19 100644
--- a/doc/appendices/command-line/traffic_ctl.en.rst
+++ b/doc/appendices/command-line/traffic_ctl.en.rst
@@ -227,7 +227,7 @@ Display the current value of a configuration record.
- **Monitor** a reload in real-time: ``traffic_ctl config reload -t -m``
- **Query** the final status: ``traffic_ctl config status -t ``
- - **Get detailed logs**: ``traffic_ctl config status -t -l``
+ - **Get detailed logs**: ``traffic_ctl config status -t ``
The timestamp of the last reconfiguration event (in seconds since epoch) is published in the
``proxy.process.proxy.reconfigure_time`` metric.
@@ -414,6 +414,58 @@ Display the current value of a configuration record.
will return an error for the corresponding key. The JSONRPC response will contain
per-key error details.
+ .. option:: --directive, -D
+
+ Pass a reload directive to a specific config handler. Directives are operational parameters
+ that modify how the handler performs the reload — for example, scoping a reload to a single
+ entry or enabling a dry-run mode. They are distinct from config content (``-d``).
+
+ The format is ``config_key.directive_key=value``, parsed by splitting on the first ``.``
+ and the first ``=``:
+
+ - ``config_key`` — the registry key (e.g. ``ip_allow``, ``sni``)
+ - ``directive_key`` — the directive name understood by that handler
+ - ``value`` — the directive value (always passed as a string on the wire)
+
+ Multiple directives are passed as space-separated values after a single ``-D``:
+
+ .. code-block:: bash
+
+ # Single directive
+ $ traffic_ctl config reload -D myconfig.id=foo
+
+ # Multiple directives for the same handler
+ $ traffic_ctl config reload -D myconfig.id=foo myconfig.dry_run=true
+
+ # Directives for different handlers in the same reload
+ $ traffic_ctl config reload -D myconfig.id=foo sni.fqdn=example.com
+
+ On the wire, ``-D myconfig.id=foo`` translates to:
+
+ .. code-block:: json
+
+ { "configs": { "myconfig": { "_reload": { "id": "foo" } } } }
+
+ For complex or nested directive values, use ``-d`` with full YAML instead:
+
+ .. code-block:: bash
+
+ $ traffic_ctl config reload -d 'myconfig: { _reload: { id: foo, options: { strict: true } } }'
+
+ .. note::
+
+ ``-D`` uses variable-argument parsing and must appear as the **last option**
+ on the command line. Any flags placed after ``-D`` will be consumed as directive
+ values. ``-D`` and ``-d`` cannot be combined in the same invocation due to this
+ same constraint. Use ``-d`` with full YAML when you need both directives and
+ inline content in a single reload request.
+
+ .. note::
+
+ Available directives depend on the handler — consult each config's documentation for
+ supported directive keys. Directive values are strings on the wire; handlers use
+ yaml-cpp's ``as()`` to interpret them as needed.
+
.. option:: --force, -F
Force a new reload even if one is already in progress. Without this flag, the server rejects
@@ -659,7 +711,8 @@ Display the current value of a configuration record.
**Failed reload report:**
When a reload has failed handlers, the output shows which handlers succeeded and which failed,
- along with durations for each:
+ along with durations and per-handler log entries. Log entries carry severity tags when a
+ severity level was recorded:
.. code-block:: bash
@@ -673,10 +726,17 @@ Display the current value of a configuration record.
Tasks:
✔ ip_allow.yaml ·························· 18ms
- ✗ logging.yaml ·························· 120ms ✗ FAIL
✗ ssl_client_coordinator ················· 85ms ✗ FAIL
- ├─ ✔ sni.yaml ··························· 20ms
- └─ ✗ ssl_multicert.config ··············· 65ms ✗ FAIL
+ │ [Note] SSL configs reloaded
+ ├─ ✔ SSLConfig ·························· 10ms
+ │ [Note] SSLConfig loading ...
+ │ [Note] SSLConfig reloaded
+ ├─ ✗ SNIConfig ·························· 12ms ✗ FAIL
+ │ [Note] sni.yaml loading ...
+ │ [Err] sni.yaml failed to load: yaml-cpp error ...
+ └─ ✔ SSLCertificateConfig ·············· 13ms
+ [Note] (ssl) ssl_multicert.yaml loading ...
+ [Note] (ssl) ssl_multicert.yaml finished loading
...
Supports the following options:
@@ -704,6 +764,78 @@ Display the current value of a configuration record.
# Show last 5 reloads
$ traffic_ctl config status -c 5
+ .. option:: --min-level
+
+ Filter task log entries by minimum severity level. Only entries at or above the specified
+ level are displayed. State-transition messages carry implicit severity:
+ ``in_progress()`` and ``complete()`` produce ``[Note]`` entries, ``fail()`` produces
+ ``[Err]`` entries. Entries without a severity (``DL_Undefined``) — typically those logged
+ via the one-argument ``ctx.log(text)`` — are always shown regardless of this filter.
+
+ Valid levels (case-insensitive): ``debug``, ``note``, ``warning``, ``error``.
+
+ .. code-block:: bash
+
+ # Show only warnings and errors
+ $ traffic_ctl config status -t my-token --min-level warning
+
+ # Show only errors
+ $ traffic_ctl config status -t my-token --min-level error
+
+ **Example — all logs (no filter):**
+
+ .. code-block:: text
+
+ ✗ ssl_client_coordinator ······················· 2ms ✗ FAIL
+ │ [Note] SSL configs reloaded
+ ├─ ✔ SSLConfig ································· 1ms
+ │ [Note] SSLConfig loading ...
+ │ [Note] SSLConfig reloaded
+ ├─ ✗ SNIConfig ································· 1ms ✗ FAIL
+ │ [Note] sni.yaml loading ...
+ │ [Err] sni.yaml failed to load
+ └─ ✔ SSLCertificateConfig ······················ 0ms
+ [Note] (ssl) ssl_multicert.yaml loading ...
+ [Warn] Cannot open SSL certificate configuration "ssl_multicert.yaml" - No such file or directory
+ [Note] (ssl) ssl_multicert.yaml finished loading
+
+ **Example — --min-level warning (note and debug entries filtered out):**
+
+ .. code-block:: text
+
+ ✗ ssl_client_coordinator ······················· 2ms ✗ FAIL
+ ├─ ✗ SNIConfig ································· 1ms ✗ FAIL
+ │ [Err] sni.yaml failed to load
+ └─ ✔ SSLCertificateConfig ······················ 0ms
+ [Warn] Cannot open SSL certificate configuration "ssl_multicert.yaml" - No such file or directory
+
+ All entries from state transitions and ``CfgLoad*`` macros carry a severity tag
+ (e.g. ``[Dbg]``, ``[Note]``, ``[Warn]``, ``[Err]``). Entries without a tag are
+ "unleveled" (from the one-argument ``ctx.log(text)``) and always pass the filter.
+
+ .. tip::
+
+ For deeper investigation beyond what ``traffic_ctl config status`` shows, enable the
+ ``config.reload`` debug tag. This writes a full dump of every subtask and its log entries
+ (with severity tags) to ``diags.log`` after each reload completes.
+ See :ref:`config-reload-diags-log` in the developer guide for details and examples.
+
+ Enable at runtime without restarting:
+
+ .. code-block:: bash
+
+ $ traffic_ctl server debug enable --tags "config.reload"
+
+ Or persistently in ``records.yaml``:
+
+ .. code-block:: yaml
+
+ records:
+ diags:
+ debug:
+ enabled: 1
+ tags: config.reload
+
**JSON output:**
All ``config status`` commands support the global ``--format json`` option to output the raw
diff --git a/doc/appendices/command-line/traffic_logcat.en.rst b/doc/appendices/command-line/traffic_logcat.en.rst
index 953f9391f3f..8538ecbda36 100644
--- a/doc/appendices/command-line/traffic_logcat.en.rst
+++ b/doc/appendices/command-line/traffic_logcat.en.rst
@@ -25,7 +25,7 @@ traffic_logcat
Synopsis
========
-:program:`traffic_logcat` [-o output-file | -a] [-CEhSVw2] [input-file ...]
+:program:`traffic_logcat` [-o output-file | -a] [-CEHhjSVw2] [input-file ...]
Description
===========
@@ -33,6 +33,9 @@ Description
To analyze a binary log file using standard tools, you must first convert
it to ASCII. :program:`traffic_logcat` does exactly that.
+:program:`traffic_logcat` reads both version 2 and version 3 binary log
+segments. See :ref:`binary-log-v3-format` for the self-describing v3 format.
+
Options
=======
@@ -74,6 +77,21 @@ Attempts to transform the input to Squid format, if possible.
Attempt to transform the input to Netscape Extended-2 format, if possible.
+.. option:: -j, --json
+
+Emits each entry as a JSON object, decoded directly from the self-describing
+v3 field-type schema (see :ref:`binary-log-v3-format`). Requires version 3
+binary logs; version 2 segments lack the schema and are skipped with a note.
+
+.. option:: -H, --header
+
+Prints the header of each binary log segment (version, format type, byte and
+entry counts, timestamps, log object signature, the format name/fieldlist/printf
+strings, source hostname, and log filename) instead of decoding entries. For
+version 3 segments the self-describing field-type schema is printed as well,
+pairing each field symbol with its framing type. Works for both version 2 and
+version 3 segments.
+
.. option:: -T, --debug_tags
.. option:: -w, --overwrite_output
diff --git a/doc/appendices/command-line/traffic_logstats.en.rst b/doc/appendices/command-line/traffic_logstats.en.rst
index 481c3d240be..9a2aca5a183 100644
--- a/doc/appendices/command-line/traffic_logstats.en.rst
+++ b/doc/appendices/command-line/traffic_logstats.en.rst
@@ -35,6 +35,9 @@ produce metrics for total and per origin requests. Currently, this utility
only supports parsing and processing the Squid binary log format, or a custom
format that is compatible with the initial log fields of the Squid format.
+Both version 2 and version 3 binary log segments are supported. See
+:ref:`binary-log-v3-format` for the self-describing v3 format.
+
Output can either be a human readable text file, or a JSON format. Parsing can
be done incrementally, and :program:`traffic_logstats` supports restarting
where it left off previously (state is stored in an external file). This is
diff --git a/doc/developer-guide/api/functions/TSHttpHdrHostGet.en.rst b/doc/developer-guide/api/functions/TSHttpHdrHostGet.en.rst
index 381add15a64..57f1e1b9828 100644
--- a/doc/developer-guide/api/functions/TSHttpHdrHostGet.en.rst
+++ b/doc/developer-guide/api/functions/TSHttpHdrHostGet.en.rst
@@ -43,3 +43,19 @@ header field.
This is much faster than calling :func:`TSHttpTxnEffectiveUrlStringGet` and
extracting the host from the result.
+
+.. note::
+
+ :func:`TSHttpHdrHostGet` checks both the URL and the ``Host`` header field,
+ making it reliable at any hook stage. In contrast, :func:`TSUrlHostGet`
+ operates only on the URL object obtained from :func:`TSHttpHdrUrlGet`. In
+ early hooks such as ``TS_HTTP_READ_REQUEST_HDR_HOOK``, the URL object may
+ not yet be fully parsed, and :func:`TSUrlHostGet` may return ``NULL`` even
+ when a ``Host`` header is present.
+
+See Also
+========
+
+:func:`TSUrlHostGet`,
+:func:`TSHttpHdrUrlGet`,
+:func:`TSHttpTxnEffectiveUrlStringGet`
diff --git a/doc/developer-guide/api/functions/TSHttpHdrUrlGet.en.rst b/doc/developer-guide/api/functions/TSHttpHdrUrlGet.en.rst
index f3ecc7737cc..ddeca6ed080 100644
--- a/doc/developer-guide/api/functions/TSHttpHdrUrlGet.en.rst
+++ b/doc/developer-guide/api/functions/TSHttpHdrUrlGet.en.rst
@@ -42,6 +42,16 @@ The value placed in :arg:`locp` is stable only for a single callback, as other c
change the URL object itself (see :func:`TSHttpHdrUrlSet`), not just the data in it. That value is
also valid only if this function return ``TS_SUCCESS``.
+.. note::
+
+ Not all URL components may be available at every hook stage. In early hooks
+ such as ``TS_HTTP_READ_REQUEST_HDR_HOOK``, the URL object may not yet be
+ fully parsed. In particular, the host component retrieved via
+ :func:`TSUrlHostGet` may be ``NULL`` even when a ``Host`` header is present.
+ For reliable host retrieval across all hook stages, use
+ :func:`TSHttpHdrHostGet` instead, which checks both the URL and the ``Host``
+ header field.
+
See Also
========
@@ -49,4 +59,6 @@ See Also
:manpage:`TSHttpTxnClientReqGet(3ts)`,
:manpage:`TSHttpTxnServerReqGet(3ts)`,
:manpage:`TSHttpTxnServerRespGet(3ts)`,
-:manpage:`TSHttpTxnClientRespGet(3ts)`
+:manpage:`TSHttpTxnClientRespGet(3ts)`,
+:manpage:`TSHttpHdrHostGet(3ts)`,
+:manpage:`TSUrlHostGet(3ts)`
diff --git a/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst b/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst
index 9a175f4e15f..43d98b40ad7 100644
--- a/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst
+++ b/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst
@@ -65,139 +65,141 @@ Testing :enumerator:`TS_CONFIG_BODY_FACTORY_TEMPLATE_BASE`.
The following configurations (from ``records.yaml``) are overridable:
-====================================================================== ====================================================================
-TSOverridableConfigKey Value Configuration Value
-====================================================================== ====================================================================
-:enumerator:`TS_CONFIG_BODY_FACTORY_TEMPLATE_BASE` :ts:cv:`proxy.config.body_factory.template_base`
-:enumerator:`TS_CONFIG_HTTP_ALLOW_HALF_OPEN` :ts:cv:`proxy.config.http.allow_half_open`
-:enumerator:`TS_CONFIG_HTTP_ALLOW_MULTI_RANGE` :ts:cv:`proxy.config.http.allow_multi_range`
-:enumerator:`TS_CONFIG_HTTP_ANONYMIZE_INSERT_CLIENT_IP` :ts:cv:`proxy.config.http.insert_client_ip`
-:enumerator:`TS_CONFIG_HTTP_ANONYMIZE_REMOVE_CLIENT_IP` :ts:cv:`proxy.config.http.anonymize_remove_client_ip`
-:enumerator:`TS_CONFIG_HTTP_ANONYMIZE_REMOVE_COOKIE` :ts:cv:`proxy.config.http.anonymize_remove_cookie`
-:enumerator:`TS_CONFIG_HTTP_ANONYMIZE_REMOVE_FROM` :ts:cv:`proxy.config.http.anonymize_remove_from`
-:enumerator:`TS_CONFIG_HTTP_ANONYMIZE_REMOVE_REFERER` :ts:cv:`proxy.config.http.anonymize_remove_referer`
-:enumerator:`TS_CONFIG_HTTP_ANONYMIZE_REMOVE_USER_AGENT` :ts:cv:`proxy.config.http.anonymize_remove_user_agent`
-:enumerator:`TS_CONFIG_HTTP_ATTACH_SERVER_SESSION_TO_CLIENT` :ts:cv:`proxy.config.http.attach_server_session_to_client`
-:enumerator:`TS_CONFIG_HTTP_MAX_PROXY_CYCLES` :ts:cv:`proxy.config.http.max_proxy_cycles`
-:enumerator:`TS_CONFIG_HTTP_AUTH_SERVER_SESSION_PRIVATE` :ts:cv:`proxy.config.http.auth_server_session_private`
-:enumerator:`TS_CONFIG_HTTP_BACKGROUND_FILL_ACTIVE_TIMEOUT` :ts:cv:`proxy.config.http.background_fill_active_timeout`
-:enumerator:`TS_CONFIG_HTTP_BACKGROUND_FILL_COMPLETED_THRESHOLD` :ts:cv:`proxy.config.http.background_fill_completed_threshold`
-:enumerator:`TS_CONFIG_HTTP_CACHE_CACHE_RESPONSES_TO_COOKIES` :ts:cv:`proxy.config.http.cache.cache_responses_to_cookies`
-:enumerator:`TS_CONFIG_HTTP_CACHE_CACHE_URLS_THAT_LOOK_DYNAMIC` :ts:cv:`proxy.config.http.cache.cache_urls_that_look_dynamic`
-:enumerator:`TS_CONFIG_HTTP_CACHE_IGNORE_QUERY` :ts:cv:`proxy.config.http.cache.ignore_query`
-:enumerator:`TS_CONFIG_HTTP_CACHE_GENERATION` :ts:cv:`proxy.config.http.cache.generation`
-:enumerator:`TS_CONFIG_HTTP_CACHE_GUARANTEED_MAX_LIFETIME` :ts:cv:`proxy.config.http.cache.guaranteed_max_lifetime`
-:enumerator:`TS_CONFIG_HTTP_CACHE_GUARANTEED_MIN_LIFETIME` :ts:cv:`proxy.config.http.cache.guaranteed_min_lifetime`
-:enumerator:`TS_CONFIG_HTTP_CACHE_HEURISTIC_LM_FACTOR` :ts:cv:`proxy.config.http.cache.heuristic_lm_factor`
-:enumerator:`TS_CONFIG_HTTP_CACHE_HEURISTIC_MAX_LIFETIME` :ts:cv:`proxy.config.http.cache.heuristic_max_lifetime`
-:enumerator:`TS_CONFIG_HTTP_CACHE_HEURISTIC_MIN_LIFETIME` :ts:cv:`proxy.config.http.cache.heuristic_min_lifetime`
-:enumerator:`TS_CONFIG_HTTP_CACHE_HTTP` :ts:cv:`proxy.config.http.cache.http`
-:enumerator:`TS_CONFIG_HTTP_CACHE_IGNORE_ACCEPT_CHARSET_MISMATCH` :ts:cv:`proxy.config.http.cache.ignore_accept_charset_mismatch`
-:enumerator:`TS_CONFIG_HTTP_CACHE_IGNORE_ACCEPT_ENCODING_MISMATCH` :ts:cv:`proxy.config.http.cache.ignore_accept_encoding_mismatch`
-:enumerator:`TS_CONFIG_HTTP_CACHE_IGNORE_ACCEPT_LANGUAGE_MISMATCH` :ts:cv:`proxy.config.http.cache.ignore_accept_language_mismatch`
-:enumerator:`TS_CONFIG_HTTP_CACHE_IGNORE_ACCEPT_MISMATCH` :ts:cv:`proxy.config.http.cache.ignore_accept_mismatch`
-:enumerator:`TS_CONFIG_HTTP_CACHE_IGNORE_AUTHENTICATION` :ts:cv:`proxy.config.http.cache.ignore_authentication`
-:enumerator:`TS_CONFIG_HTTP_CACHE_IGNORE_CLIENT_CC_MAX_AGE` :ts:cv:`proxy.config.http.cache.ignore_client_cc_max_age`
-:enumerator:`TS_CONFIG_HTTP_CACHE_IGNORE_CLIENT_NO_CACHE` :ts:cv:`proxy.config.http.cache.ignore_client_no_cache`
-:enumerator:`TS_CONFIG_HTTP_CACHE_IGNORE_SERVER_NO_CACHE` :ts:cv:`proxy.config.http.cache.ignore_server_no_cache`
-:enumerator:`TS_CONFIG_HTTP_CACHE_IMS_ON_CLIENT_NO_CACHE` :ts:cv:`proxy.config.http.cache.ims_on_client_no_cache`
-:enumerator:`TS_CONFIG_HTTP_CACHE_MAX_OPEN_READ_RETRIES` :ts:cv:`proxy.config.http.cache.max_open_read_retries`
-:enumerator:`TS_CONFIG_HTTP_CACHE_MAX_OPEN_WRITE_RETRIES` :ts:cv:`proxy.config.http.cache.max_open_write_retries`
-:enumerator:`TS_CONFIG_HTTP_CACHE_MAX_STALE_AGE` :ts:cv:`proxy.config.http.cache.max_stale_age`
-:enumerator:`TS_CONFIG_HTTP_CACHE_OPEN_READ_RETRY_TIME` :ts:cv:`proxy.config.http.cache.open_read_retry_time`
-:enumerator:`TS_CONFIG_HTTP_CACHE_OPEN_WRITE_FAIL_ACTION` :ts:cv:`proxy.config.http.cache.open_write_fail_action`
-:enumerator:`TS_CONFIG_HTTP_CACHE_RANGE_LOOKUP` :ts:cv:`proxy.config.http.cache.range.lookup`
-:enumerator:`TS_CONFIG_HTTP_CACHE_RANGE_WRITE` :ts:cv:`proxy.config.http.cache.range.write`
-:enumerator:`TS_CONFIG_HTTP_CACHE_REQUIRED_HEADERS` :ts:cv:`proxy.config.http.cache.required_headers`
-:enumerator:`TS_CONFIG_HTTP_CACHE_WHEN_TO_REVALIDATE` :ts:cv:`proxy.config.http.cache.when_to_revalidate`
-:enumerator:`TS_CONFIG_HTTP_CHUNKING_ENABLED` :ts:cv:`proxy.config.http.chunking_enabled`
-:enumerator:`TS_CONFIG_HTTP_CHUNKING_SIZE` :ts:cv:`proxy.config.http.chunking.size`
-:enumerator:`TS_CONFIG_HTTP_STRICT_CHUNK_PARSING` :ts:cv:`proxy.config.http.strict_chunk_parsing`
-:enumerator:`TS_CONFIG_HTTP_DROP_CHUNKED_TRAILERS` :ts:cv:`proxy.config.http.drop_chunked_trailers`
-:enumerator:`TS_CONFIG_HTTP_CONNECT_ATTEMPTS_MAX_RETRIES_DOWN_SERVER` :ts:cv:`proxy.config.http.connect_attempts_max_retries_down_server`
-:enumerator:`TS_CONFIG_HTTP_CONNECT_ATTEMPTS_MAX_RETRIES` :ts:cv:`proxy.config.http.connect_attempts_max_retries`
-:enumerator:`TS_CONFIG_HTTP_CONNECT_ATTEMPTS_RR_RETRIES` :ts:cv:`proxy.config.http.connect_attempts_rr_retries`
-:enumerator:`TS_CONFIG_HTTP_CONNECT_ATTEMPTS_TIMEOUT` :ts:cv:`proxy.config.http.connect_attempts_timeout`
-:enumerator:`TS_CONFIG_HTTP_CONNECT_ATTEMPTS_RETRY_BACKOFF_BASE` :ts:cv:`proxy.config.http.connect_attempts_retry_backoff_base`
-:enumerator:`TS_CONFIG_HTTP_DEFAULT_BUFFER_SIZE` :ts:cv:`proxy.config.http.default_buffer_size`
-:enumerator:`TS_CONFIG_HTTP_DEFAULT_BUFFER_WATER_MARK` :ts:cv:`proxy.config.http.default_buffer_water_mark`
-:enumerator:`TS_CONFIG_HTTP_DOC_IN_CACHE_SKIP_DNS` :ts:cv:`proxy.config.http.doc_in_cache_skip_dns`
-:enumerator:`TS_CONFIG_HTTP_DOWN_SERVER_CACHE_TIME` :ts:cv:`proxy.config.http.down_server.cache_time`
-:enumerator:`TS_CONFIG_HTTP_FLOW_CONTROL_ENABLED` :ts:cv:`proxy.config.http.flow_control.enabled`
-:enumerator:`TS_CONFIG_HTTP_FLOW_CONTROL_HIGH_WATER_MARK` :ts:cv:`proxy.config.http.flow_control.high_water`
-:enumerator:`TS_CONFIG_HTTP_FLOW_CONTROL_LOW_WATER_MARK` :ts:cv:`proxy.config.http.flow_control.low_water`
-:enumerator:`TS_CONFIG_HTTP_FORWARD_CONNECT_METHOD` :ts:cv:`proxy.config.http.forward_connect_method`
-:enumerator:`TS_CONFIG_HTTP_FORWARD_PROXY_AUTH_TO_PARENT` :ts:cv:`proxy.config.http.forward.proxy_auth_to_parent`
-:enumerator:`TS_CONFIG_HTTP_GLOBAL_USER_AGENT_HEADER` :ts:cv:`proxy.config.http.global_user_agent_header`
-:enumerator:`TS_CONFIG_HTTP_INSERT_AGE_IN_RESPONSE` :ts:cv:`proxy.config.http.insert_age_in_response`
-:enumerator:`TS_CONFIG_HTTP_INSERT_FORWARDED` :ts:cv:`proxy.config.http.insert_forwarded`
-:enumerator:`TS_CONFIG_HTTP_INSERT_REQUEST_VIA_STR` :ts:cv:`proxy.config.http.insert_request_via_str`
-:enumerator:`TS_CONFIG_HTTP_INSERT_RESPONSE_VIA_STR` :ts:cv:`proxy.config.http.insert_response_via_str`
-:enumerator:`TS_CONFIG_HTTP_INSERT_SQUID_X_FORWARDED_FOR` :ts:cv:`proxy.config.http.insert_squid_x_forwarded_for`
-:enumerator:`TS_CONFIG_HTTP_KEEP_ALIVE_ENABLED_IN` :ts:cv:`proxy.config.http.keep_alive_enabled_in`
-:enumerator:`TS_CONFIG_HTTP_KEEP_ALIVE_ENABLED_OUT` :ts:cv:`proxy.config.http.keep_alive_enabled_out`
-:enumerator:`TS_CONFIG_HTTP_KEEP_ALIVE_NO_ACTIVITY_TIMEOUT_IN` :ts:cv:`proxy.config.http.keep_alive_no_activity_timeout_in`
-:enumerator:`TS_CONFIG_HTTP_KEEP_ALIVE_NO_ACTIVITY_TIMEOUT_OUT` :ts:cv:`proxy.config.http.keep_alive_no_activity_timeout_out`
-:enumerator:`TS_CONFIG_HTTP_KEEP_ALIVE_POST_OUT` :ts:cv:`proxy.config.http.keep_alive_post_out`
-:enumerator:`TS_CONFIG_HTTP_NEGATIVE_CACHING_ENABLED` :ts:cv:`proxy.config.http.negative_caching_enabled`
-:enumerator:`TS_CONFIG_HTTP_NEGATIVE_CACHING_LIFETIME` :ts:cv:`proxy.config.http.negative_caching_lifetime`
-:enumerator:`TS_CONFIG_HTTP_NEGATIVE_CACHING_LIST` :ts:cv:`proxy.config.http.negative_caching_list`
-:enumerator:`TS_CONFIG_HTTP_NEGATIVE_REVALIDATING_ENABLED` :ts:cv:`proxy.config.http.negative_revalidating_enabled`
-:enumerator:`TS_CONFIG_HTTP_NEGATIVE_REVALIDATING_LIFETIME` :ts:cv:`proxy.config.http.negative_revalidating_lifetime`
-:enumerator:`TS_CONFIG_HTTP_NEGATIVE_REVALIDATING_LIST` :ts:cv:`proxy.config.http.negative_revalidating_list`
-:enumerator:`TS_CONFIG_HTTP_NO_DNS_JUST_FORWARD_TO_PARENT` :ts:cv:`proxy.config.http.no_dns_just_forward_to_parent`
-:enumerator:`TS_CONFIG_HTTP_NORMALIZE_AE` :ts:cv:`proxy.config.http.normalize_ae`
-:enumerator:`TS_CONFIG_HTTP_NUMBER_OF_REDIRECTIONS` :ts:cv:`proxy.config.http.number_of_redirections`
-:enumerator:`TS_CONFIG_HTTP_PARENT_PROXY_FAIL_THRESHOLD` :ts:cv:`proxy.config.http.parent_proxy.fail_threshold`
-:enumerator:`TS_CONFIG_HTTP_PARENT_PROXY_RETRY_TIME` :ts:cv:`proxy.config.http.parent_proxy.retry_time`
-:enumerator:`TS_CONFIG_HTTP_PARENT_PROXY_TOTAL_CONNECT_ATTEMPTS` :ts:cv:`proxy.config.http.parent_proxy.total_connect_attempts`
-:enumerator:`TS_CONFIG_HTTP_PER_PARENT_CONNECT_ATTEMPTS` :ts:cv:`proxy.config.http.parent_proxy.per_parent_connect_attempts`
-:enumerator:`TS_CONFIG_HTTP_PER_SERVER_CONNECTION_MATCH` :ts:cv:`proxy.config.http.per_server.connection.match`
-:enumerator:`TS_CONFIG_HTTP_PER_SERVER_CONNECTION_MAX` :ts:cv:`proxy.config.http.per_server.connection.max`
-:enumerator:`TS_CONFIG_HTTP_POST_CHECK_CONTENT_LENGTH_ENABLED` :ts:cv:`proxy.config.http.post.check.content_length.enabled`
-:enumerator:`TS_CONFIG_HTTP_REDIRECT_USE_ORIG_CACHE_KEY` :ts:cv:`proxy.config.http.redirect_use_orig_cache_key`
-:enumerator:`TS_CONFIG_HTTP_REQUEST_BUFFER_ENABLED` :ts:cv:`proxy.config.http.request_buffer_enabled`
-:enumerator:`TS_CONFIG_HTTP_REQUEST_HEADER_MAX_SIZE` :ts:cv:`proxy.config.http.request_header_max_size`
-:enumerator:`TS_CONFIG_HTTP_RESPONSE_HEADER_MAX_SIZE` :ts:cv:`proxy.config.http.response_header_max_size`
-:enumerator:`TS_CONFIG_HTTP_RESPONSE_SERVER_ENABLED` :ts:cv:`proxy.config.http.response_server_enabled`
-:enumerator:`TS_CONFIG_HTTP_RESPONSE_SERVER_STR` :ts:cv:`proxy.config.http.response_server_str`
-:enumerator:`TS_CONFIG_HTTP_SEND_HTTP11_REQUESTS` :ts:cv:`proxy.config.http.send_http11_requests`
-:enumerator:`TS_CONFIG_HTTP_SERVER_SESSION_SHARING_MATCH` :ts:cv:`proxy.config.http.server_session_sharing.match`
-:enumerator:`TS_CONFIG_HTTP_SLOW_LOG_THRESHOLD` :ts:cv:`proxy.config.http.slow.log.threshold`
-:enumerator:`TS_CONFIG_HTTP_TRANSACTION_ACTIVE_TIMEOUT_IN` :ts:cv:`proxy.config.http.transaction_active_timeout_in`
-:enumerator:`TS_CONFIG_HTTP_TRANSACTION_ACTIVE_TIMEOUT_OUT` :ts:cv:`proxy.config.http.transaction_active_timeout_out`
-:enumerator:`TS_CONFIG_HTTP_TRANSACTION_NO_ACTIVITY_TIMEOUT_IN` :ts:cv:`proxy.config.http.transaction_no_activity_timeout_in`
-:enumerator:`TS_CONFIG_HTTP_TRANSACTION_NO_ACTIVITY_TIMEOUT_OUT` :ts:cv:`proxy.config.http.transaction_no_activity_timeout_out`
-:enumerator:`TS_CONFIG_HTTP_UNCACHEABLE_REQUESTS_BYPASS_PARENT` :ts:cv:`proxy.config.http.uncacheable_requests_bypass_parent`
-:enumerator:`TS_CONFIG_NET_SOCK_OPTION_FLAG_OUT` :ts:cv:`proxy.config.net.sock_option_flag_out`
-:enumerator:`TS_CONFIG_NET_SOCK_PACKET_MARK_OUT` :ts:cv:`proxy.config.net.sock_packet_mark_out`
-:enumerator:`TS_CONFIG_NET_SOCK_PACKET_TOS_OUT` :ts:cv:`proxy.config.net.sock_packet_tos_out`
-:enumerator:`TS_CONFIG_NET_SOCK_RECV_BUFFER_SIZE_OUT` :ts:cv:`proxy.config.net.sock_recv_buffer_size_out`
-:enumerator:`TS_CONFIG_NET_DEFAULT_INACTIVITY_TIMEOUT` :ts:cv:`proxy.config.net.default_inactivity_timeout`
-:enumerator:`TS_CONFIG_NET_SOCK_SEND_BUFFER_SIZE_OUT` :ts:cv:`proxy.config.net.sock_send_buffer_size_out`
-:enumerator:`TS_CONFIG_PARENT_FAILURES_UPDATE_HOSTDB` :ts:cv:`proxy.config.http.parent_proxy.mark_down_hostdb`
-:enumerator:`TS_CONFIG_SRV_ENABLED` :ts:cv:`proxy.config.srv_enabled`
-:enumerator:`TS_CONFIG_SSL_CLIENT_CERT_FILENAME` :ts:cv:`proxy.config.ssl.client.cert.filename`
-:enumerator:`TS_CONFIG_SSL_CERT_FILEPATH` :ts:cv:`proxy.config.ssl.client.cert.path`
-:enumerator:`TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_PROPERTIES` :ts:cv:`proxy.config.ssl.client.verify.server.properties`
-:enumerator:`TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_POLICY` :ts:cv:`proxy.config.ssl.client.verify.server.policy`
-:enumerator:`TS_CONFIG_SSL_CLIENT_SNI_POLICY` :ts:cv:`proxy.config.ssl.client.sni_policy`
-:enumerator:`TS_CONFIG_SSL_HSTS_INCLUDE_SUBDOMAINS` :ts:cv:`proxy.config.ssl.hsts_include_subdomains`
-:enumerator:`TS_CONFIG_SSL_HSTS_MAX_AGE` :ts:cv:`proxy.config.ssl.hsts_max_age`
-:enumerator:`TS_CONFIG_URL_REMAP_PRISTINE_HOST_HDR` :ts:cv:`proxy.config.url_remap.pristine_host_hdr`
-:enumerator:`TS_CONFIG_WEBSOCKET_ACTIVE_TIMEOUT` :ts:cv:`proxy.config.websocket.active_timeout`
-:enumerator:`TS_CONFIG_WEBSOCKET_NO_ACTIVITY_TIMEOUT` :ts:cv:`proxy.config.websocket.no_activity_timeout`
-:enumerator:`TS_CONFIG_SSL_CLIENT_CERT_FILENAME` :ts:cv:`proxy.config.ssl.client.cert.filename`
-:enumerator:`TS_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME` :ts:cv:`proxy.config.ssl.client.private_key.filename`
-:enumerator:`TS_CONFIG_SSL_CLIENT_CA_CERT_FILENAME` :ts:cv:`proxy.config.ssl.client.CA.cert.filename`
-:enumerator:`TS_CONFIG_HTTP_HOST_RESOLUTION_PREFERENCE` :ts:cv:`proxy.config.hostdb.ip_resolve`
-:enumerator:`TS_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_INDEX` :ts:cv:`proxy.config.plugin.vc.default_buffer_index`
-:enumerator:`TS_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_WATER_MARK` :ts:cv:`proxy.config.plugin.vc.default_buffer_water_mark`
-:enumerator:`TS_CONFIG_NET_SOCK_NOTSENT_LOWAT` :ts:cv:`proxy.config.net.sock_notsent_lowat`
-:enumerator:`TS_CONFIG_BODY_FACTORY_RESPONSE_SUPPRESSION_MODE` :ts:cv:`proxy.config.body_factory.response_suppression_mode`
-:enumerator:`TS_CONFIG_HTTP_CACHE_POST_METHOD` :ts:cv:`proxy.config.http.cache.post_method`
-:enumerator:`TS_CONFIG_HTTP_CACHE_TARGETED_CACHE_CONTROL_HEADERS` :ts:cv:`proxy.config.http.cache.targeted_cache_control_headers`
-====================================================================== ====================================================================
+======================================================================== ====================================================================
+TSOverridableConfigKey Value Configuration Value
+======================================================================== ====================================================================
+:enumerator:`TS_CONFIG_BODY_FACTORY_TEMPLATE_BASE` :ts:cv:`proxy.config.body_factory.template_base`
+:enumerator:`TS_CONFIG_HTTP_ALLOW_HALF_OPEN` :ts:cv:`proxy.config.http.allow_half_open`
+:enumerator:`TS_CONFIG_HTTP_ALLOW_MULTI_RANGE` :ts:cv:`proxy.config.http.allow_multi_range`
+:enumerator:`TS_CONFIG_HTTP_ANONYMIZE_INSERT_CLIENT_IP` :ts:cv:`proxy.config.http.insert_client_ip`
+:enumerator:`TS_CONFIG_HTTP_ANONYMIZE_REMOVE_CLIENT_IP` :ts:cv:`proxy.config.http.anonymize_remove_client_ip`
+:enumerator:`TS_CONFIG_HTTP_ANONYMIZE_REMOVE_COOKIE` :ts:cv:`proxy.config.http.anonymize_remove_cookie`
+:enumerator:`TS_CONFIG_HTTP_ANONYMIZE_REMOVE_FROM` :ts:cv:`proxy.config.http.anonymize_remove_from`
+:enumerator:`TS_CONFIG_HTTP_ANONYMIZE_REMOVE_REFERER` :ts:cv:`proxy.config.http.anonymize_remove_referer`
+:enumerator:`TS_CONFIG_HTTP_ANONYMIZE_REMOVE_USER_AGENT` :ts:cv:`proxy.config.http.anonymize_remove_user_agent`
+:enumerator:`TS_CONFIG_HTTP_ATTACH_SERVER_SESSION_TO_CLIENT` :ts:cv:`proxy.config.http.attach_server_session_to_client`
+:enumerator:`TS_CONFIG_HTTP_MAX_PROXY_CYCLES` :ts:cv:`proxy.config.http.max_proxy_cycles`
+:enumerator:`TS_CONFIG_HTTP_AUTH_SERVER_SESSION_PRIVATE` :ts:cv:`proxy.config.http.auth_server_session_private`
+:enumerator:`TS_CONFIG_HTTP_BACKGROUND_FILL_ACTIVE_TIMEOUT` :ts:cv:`proxy.config.http.background_fill_active_timeout`
+:enumerator:`TS_CONFIG_HTTP_BACKGROUND_FILL_COMPLETED_THRESHOLD` :ts:cv:`proxy.config.http.background_fill_completed_threshold`
+:enumerator:`TS_CONFIG_HTTP_CACHE_CACHE_RESPONSES_TO_COOKIES` :ts:cv:`proxy.config.http.cache.cache_responses_to_cookies`
+:enumerator:`TS_CONFIG_HTTP_CACHE_CACHE_URLS_THAT_LOOK_DYNAMIC` :ts:cv:`proxy.config.http.cache.cache_urls_that_look_dynamic`
+:enumerator:`TS_CONFIG_HTTP_CACHE_IGNORE_QUERY` :ts:cv:`proxy.config.http.cache.ignore_query`
+:enumerator:`TS_CONFIG_HTTP_CACHE_GENERATION` :ts:cv:`proxy.config.http.cache.generation`
+:enumerator:`TS_CONFIG_HTTP_CACHE_GUARANTEED_MAX_LIFETIME` :ts:cv:`proxy.config.http.cache.guaranteed_max_lifetime`
+:enumerator:`TS_CONFIG_HTTP_CACHE_GUARANTEED_MIN_LIFETIME` :ts:cv:`proxy.config.http.cache.guaranteed_min_lifetime`
+:enumerator:`TS_CONFIG_HTTP_CACHE_HEURISTIC_LM_FACTOR` :ts:cv:`proxy.config.http.cache.heuristic_lm_factor`
+:enumerator:`TS_CONFIG_HTTP_CACHE_HEURISTIC_MAX_LIFETIME` :ts:cv:`proxy.config.http.cache.heuristic_max_lifetime`
+:enumerator:`TS_CONFIG_HTTP_CACHE_HEURISTIC_MIN_LIFETIME` :ts:cv:`proxy.config.http.cache.heuristic_min_lifetime`
+:enumerator:`TS_CONFIG_HTTP_CACHE_HTTP` :ts:cv:`proxy.config.http.cache.http`
+:enumerator:`TS_CONFIG_HTTP_CACHE_IGNORE_ACCEPT_CHARSET_MISMATCH` :ts:cv:`proxy.config.http.cache.ignore_accept_charset_mismatch`
+:enumerator:`TS_CONFIG_HTTP_CACHE_IGNORE_ACCEPT_ENCODING_MISMATCH` :ts:cv:`proxy.config.http.cache.ignore_accept_encoding_mismatch`
+:enumerator:`TS_CONFIG_HTTP_CACHE_IGNORE_ACCEPT_LANGUAGE_MISMATCH` :ts:cv:`proxy.config.http.cache.ignore_accept_language_mismatch`
+:enumerator:`TS_CONFIG_HTTP_CACHE_IGNORE_ACCEPT_MISMATCH` :ts:cv:`proxy.config.http.cache.ignore_accept_mismatch`
+:enumerator:`TS_CONFIG_HTTP_CACHE_IGNORE_AUTHENTICATION` :ts:cv:`proxy.config.http.cache.ignore_authentication`
+:enumerator:`TS_CONFIG_HTTP_CACHE_IGNORE_CLIENT_CC_MAX_AGE` :ts:cv:`proxy.config.http.cache.ignore_client_cc_max_age`
+:enumerator:`TS_CONFIG_HTTP_CACHE_IGNORE_CLIENT_NO_CACHE` :ts:cv:`proxy.config.http.cache.ignore_client_no_cache`
+:enumerator:`TS_CONFIG_HTTP_CACHE_IGNORE_SERVER_NO_CACHE` :ts:cv:`proxy.config.http.cache.ignore_server_no_cache`
+:enumerator:`TS_CONFIG_HTTP_CACHE_IMS_ON_CLIENT_NO_CACHE` :ts:cv:`proxy.config.http.cache.ims_on_client_no_cache`
+:enumerator:`TS_CONFIG_HTTP_CACHE_MAX_OPEN_READ_RETRIES` :ts:cv:`proxy.config.http.cache.max_open_read_retries`
+:enumerator:`TS_CONFIG_HTTP_CACHE_MAX_OPEN_WRITE_RETRIES` :ts:cv:`proxy.config.http.cache.max_open_write_retries`
+:enumerator:`TS_CONFIG_HTTP_CACHE_MAX_STALE_AGE` :ts:cv:`proxy.config.http.cache.max_stale_age`
+:enumerator:`TS_CONFIG_HTTP_CACHE_OPEN_READ_RETRY_TIME` :ts:cv:`proxy.config.http.cache.open_read_retry_time`
+:enumerator:`TS_CONFIG_HTTP_CACHE_OPEN_WRITE_FAIL_ACTION` :ts:cv:`proxy.config.http.cache.open_write_fail_action`
+:enumerator:`TS_CONFIG_HTTP_CACHE_RANGE_LOOKUP` :ts:cv:`proxy.config.http.cache.range.lookup`
+:enumerator:`TS_CONFIG_HTTP_CACHE_RANGE_WRITE` :ts:cv:`proxy.config.http.cache.range.write`
+:enumerator:`TS_CONFIG_HTTP_CACHE_REQUIRED_HEADERS` :ts:cv:`proxy.config.http.cache.required_headers`
+:enumerator:`TS_CONFIG_HTTP_CACHE_WHEN_TO_REVALIDATE` :ts:cv:`proxy.config.http.cache.when_to_revalidate`
+:enumerator:`TS_CONFIG_HTTP_CHUNKING_ENABLED` :ts:cv:`proxy.config.http.chunking_enabled`
+:enumerator:`TS_CONFIG_HTTP_CHUNKING_SIZE` :ts:cv:`proxy.config.http.chunking.size`
+:enumerator:`TS_CONFIG_HTTP_STRICT_CHUNK_PARSING` :ts:cv:`proxy.config.http.strict_chunk_parsing`
+:enumerator:`TS_CONFIG_HTTP_DROP_CHUNKED_TRAILERS` :ts:cv:`proxy.config.http.drop_chunked_trailers`
+:enumerator:`TS_CONFIG_HTTP_CONNECT_ATTEMPTS_MAX_RETRIES_DOWN_SERVER` :ts:cv:`proxy.config.http.connect_attempts_max_retries_down_server`
+:enumerator:`TS_CONFIG_HTTP_CONNECT_ATTEMPTS_MAX_RETRIES_SUSPECT_SERVER` :ts:cv:`proxy.config.http.connect_attempts_max_retries_suspect_server`
+:enumerator:`TS_CONFIG_HTTP_CONNECT_ATTEMPTS_MAX_RETRIES` :ts:cv:`proxy.config.http.connect_attempts_max_retries`
+:enumerator:`TS_CONFIG_HTTP_CONNECT_ATTEMPTS_RR_RETRIES` :ts:cv:`proxy.config.http.connect_attempts_rr_retries`
+:enumerator:`TS_CONFIG_HTTP_CONNECT_ATTEMPTS_TIMEOUT` :ts:cv:`proxy.config.http.connect_attempts_timeout`
+:enumerator:`TS_CONFIG_HTTP_CONNECT_ATTEMPTS_RETRY_BACKOFF_BASE` :ts:cv:`proxy.config.http.connect_attempts_retry_backoff_base`
+:enumerator:`TS_CONFIG_HTTP_DEFAULT_BUFFER_SIZE` :ts:cv:`proxy.config.http.default_buffer_size`
+:enumerator:`TS_CONFIG_HTTP_DEFAULT_BUFFER_WATER_MARK` :ts:cv:`proxy.config.http.default_buffer_water_mark`
+:enumerator:`TS_CONFIG_HTTP_DOC_IN_CACHE_SKIP_DNS` :ts:cv:`proxy.config.http.doc_in_cache_skip_dns`
+:enumerator:`TS_CONFIG_HTTP_DOWN_SERVER_CACHE_TIME` :ts:cv:`proxy.config.http.down_server.cache_time`
+:enumerator:`TS_CONFIG_HTTP_FLOW_CONTROL_ENABLED` :ts:cv:`proxy.config.http.flow_control.enabled`
+:enumerator:`TS_CONFIG_HTTP_FLOW_CONTROL_HIGH_WATER_MARK` :ts:cv:`proxy.config.http.flow_control.high_water`
+:enumerator:`TS_CONFIG_HTTP_FLOW_CONTROL_LOW_WATER_MARK` :ts:cv:`proxy.config.http.flow_control.low_water`
+:enumerator:`TS_CONFIG_HTTP_FORWARD_CONNECT_METHOD` :ts:cv:`proxy.config.http.forward_connect_method`
+:enumerator:`TS_CONFIG_HTTP_FORWARD_PROXY_AUTH_TO_PARENT` :ts:cv:`proxy.config.http.forward.proxy_auth_to_parent`
+:enumerator:`TS_CONFIG_HTTP_GLOBAL_USER_AGENT_HEADER` :ts:cv:`proxy.config.http.global_user_agent_header`
+:enumerator:`TS_CONFIG_HTTP_INSERT_AGE_IN_RESPONSE` :ts:cv:`proxy.config.http.insert_age_in_response`
+:enumerator:`TS_CONFIG_HTTP_INSERT_FORWARDED` :ts:cv:`proxy.config.http.insert_forwarded`
+:enumerator:`TS_CONFIG_HTTP_INSERT_REQUEST_VIA_STR` :ts:cv:`proxy.config.http.insert_request_via_str`
+:enumerator:`TS_CONFIG_HTTP_INSERT_RESPONSE_VIA_STR` :ts:cv:`proxy.config.http.insert_response_via_str`
+:enumerator:`TS_CONFIG_HTTP_INSERT_SQUID_X_FORWARDED_FOR` :ts:cv:`proxy.config.http.insert_squid_x_forwarded_for`
+:enumerator:`TS_CONFIG_HTTP_KEEP_ALIVE_ENABLED_IN` :ts:cv:`proxy.config.http.keep_alive_enabled_in`
+:enumerator:`TS_CONFIG_HTTP_KEEP_ALIVE_ENABLED_OUT` :ts:cv:`proxy.config.http.keep_alive_enabled_out`
+:enumerator:`TS_CONFIG_HTTP_KEEP_ALIVE_NO_ACTIVITY_TIMEOUT_IN` :ts:cv:`proxy.config.http.keep_alive_no_activity_timeout_in`
+:enumerator:`TS_CONFIG_HTTP_KEEP_ALIVE_NO_ACTIVITY_TIMEOUT_OUT` :ts:cv:`proxy.config.http.keep_alive_no_activity_timeout_out`
+:enumerator:`TS_CONFIG_HTTP_KEEP_ALIVE_POST_OUT` :ts:cv:`proxy.config.http.keep_alive_post_out`
+:enumerator:`TS_CONFIG_HTTP_NEGATIVE_CACHING_ENABLED` :ts:cv:`proxy.config.http.negative_caching_enabled`
+:enumerator:`TS_CONFIG_HTTP_NEGATIVE_CACHING_LIFETIME` :ts:cv:`proxy.config.http.negative_caching_lifetime`
+:enumerator:`TS_CONFIG_HTTP_NEGATIVE_CACHING_LIST` :ts:cv:`proxy.config.http.negative_caching_list`
+:enumerator:`TS_CONFIG_HTTP_NEGATIVE_REVALIDATING_ENABLED` :ts:cv:`proxy.config.http.negative_revalidating_enabled`
+:enumerator:`TS_CONFIG_HTTP_NEGATIVE_REVALIDATING_LIFETIME` :ts:cv:`proxy.config.http.negative_revalidating_lifetime`
+:enumerator:`TS_CONFIG_HTTP_NEGATIVE_REVALIDATING_LIST` :ts:cv:`proxy.config.http.negative_revalidating_list`
+:enumerator:`TS_CONFIG_HTTP_NO_DNS_JUST_FORWARD_TO_PARENT` :ts:cv:`proxy.config.http.no_dns_just_forward_to_parent`
+:enumerator:`TS_CONFIG_HTTP_NORMALIZE_AE` :ts:cv:`proxy.config.http.normalize_ae`
+:enumerator:`TS_CONFIG_HTTP_NUMBER_OF_REDIRECTIONS` :ts:cv:`proxy.config.http.number_of_redirections`
+:enumerator:`TS_CONFIG_HTTP_PARENT_PROXY_FAIL_THRESHOLD` :ts:cv:`proxy.config.http.parent_proxy.fail_threshold`
+:enumerator:`TS_CONFIG_HTTP_PARENT_PROXY_RETRY_TIME` :ts:cv:`proxy.config.http.parent_proxy.retry_time`
+:enumerator:`TS_CONFIG_HTTP_PARENT_PROXY_TOTAL_CONNECT_ATTEMPTS` :ts:cv:`proxy.config.http.parent_proxy.total_connect_attempts`
+:enumerator:`TS_CONFIG_HTTP_PER_PARENT_CONNECT_ATTEMPTS` :ts:cv:`proxy.config.http.parent_proxy.per_parent_connect_attempts`
+:enumerator:`TS_CONFIG_HTTP_PER_SERVER_CONNECTION_MATCH` :ts:cv:`proxy.config.http.per_server.connection.match`
+:enumerator:`TS_CONFIG_HTTP_PER_SERVER_CONNECTION_MAX` :ts:cv:`proxy.config.http.per_server.connection.max`
+:enumerator:`TS_CONFIG_HTTP_POST_CHECK_CONTENT_LENGTH_ENABLED` :ts:cv:`proxy.config.http.post.check.content_length.enabled`
+:enumerator:`TS_CONFIG_HTTP_REDIRECT_USE_ORIG_CACHE_KEY` :ts:cv:`proxy.config.http.redirect_use_orig_cache_key`
+:enumerator:`TS_CONFIG_HTTP_REQUEST_BUFFER_ENABLED` :ts:cv:`proxy.config.http.request_buffer_enabled`
+:enumerator:`TS_CONFIG_HTTP_REQUEST_HEADER_MAX_SIZE` :ts:cv:`proxy.config.http.request_header_max_size`
+:enumerator:`TS_CONFIG_HTTP_RESPONSE_HEADER_MAX_SIZE` :ts:cv:`proxy.config.http.response_header_max_size`
+:enumerator:`TS_CONFIG_HTTP_RESPONSE_SERVER_ENABLED` :ts:cv:`proxy.config.http.response_server_enabled`
+:enumerator:`TS_CONFIG_HTTP_RESPONSE_SERVER_STR` :ts:cv:`proxy.config.http.response_server_str`
+:enumerator:`TS_CONFIG_HTTP_SEND_HTTP11_REQUESTS` :ts:cv:`proxy.config.http.send_http11_requests`
+:enumerator:`TS_CONFIG_HTTP_SERVER_SESSION_SHARING_MATCH` :ts:cv:`proxy.config.http.server_session_sharing.match`
+:enumerator:`TS_CONFIG_HTTP_SLOW_LOG_THRESHOLD` :ts:cv:`proxy.config.http.slow.log.threshold`
+:enumerator:`TS_CONFIG_HTTP_TRANSACTION_ACTIVE_TIMEOUT_IN` :ts:cv:`proxy.config.http.transaction_active_timeout_in`
+:enumerator:`TS_CONFIG_HTTP_TRANSACTION_ACTIVE_TIMEOUT_OUT` :ts:cv:`proxy.config.http.transaction_active_timeout_out`
+:enumerator:`TS_CONFIG_HTTP_TRANSACTION_NO_ACTIVITY_TIMEOUT_IN` :ts:cv:`proxy.config.http.transaction_no_activity_timeout_in`
+:enumerator:`TS_CONFIG_HTTP_TRANSACTION_NO_ACTIVITY_TIMEOUT_OUT` :ts:cv:`proxy.config.http.transaction_no_activity_timeout_out`
+:enumerator:`TS_CONFIG_HTTP_UNCACHEABLE_REQUESTS_BYPASS_PARENT` :ts:cv:`proxy.config.http.uncacheable_requests_bypass_parent`
+:enumerator:`TS_CONFIG_NET_SOCK_OPTION_FLAG_OUT` :ts:cv:`proxy.config.net.sock_option_flag_out`
+:enumerator:`TS_CONFIG_NET_SOCK_PACKET_MARK_OUT` :ts:cv:`proxy.config.net.sock_packet_mark_out`
+:enumerator:`TS_CONFIG_NET_SOCK_PACKET_TOS_OUT` :ts:cv:`proxy.config.net.sock_packet_tos_out`
+:enumerator:`TS_CONFIG_NET_SOCK_RECV_BUFFER_SIZE_OUT` :ts:cv:`proxy.config.net.sock_recv_buffer_size_out`
+:enumerator:`TS_CONFIG_NET_DEFAULT_INACTIVITY_TIMEOUT` :ts:cv:`proxy.config.net.default_inactivity_timeout`
+:enumerator:`TS_CONFIG_NET_SOCK_SEND_BUFFER_SIZE_OUT` :ts:cv:`proxy.config.net.sock_send_buffer_size_out`
+:enumerator:`TS_CONFIG_PARENT_FAILURES_UPDATE_HOSTDB` :ts:cv:`proxy.config.http.parent_proxy.mark_down_hostdb`
+:enumerator:`TS_CONFIG_SRV_ENABLED` :ts:cv:`proxy.config.srv_enabled`
+:enumerator:`TS_CONFIG_SSL_CLIENT_CERT_FILENAME` :ts:cv:`proxy.config.ssl.client.cert.filename`
+:enumerator:`TS_CONFIG_SSL_CERT_FILEPATH` :ts:cv:`proxy.config.ssl.client.cert.path`
+:enumerator:`TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_PROPERTIES` :ts:cv:`proxy.config.ssl.client.verify.server.properties`
+:enumerator:`TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_POLICY` :ts:cv:`proxy.config.ssl.client.verify.server.policy`
+:enumerator:`TS_CONFIG_SSL_CLIENT_SNI_POLICY` :ts:cv:`proxy.config.ssl.client.sni_policy`
+:enumerator:`TS_CONFIG_SSL_HSTS_INCLUDE_SUBDOMAINS` :ts:cv:`proxy.config.ssl.hsts_include_subdomains`
+:enumerator:`TS_CONFIG_SSL_HSTS_MAX_AGE` :ts:cv:`proxy.config.ssl.hsts_max_age`
+:enumerator:`TS_CONFIG_URL_REMAP_PRISTINE_HOST_HDR` :ts:cv:`proxy.config.url_remap.pristine_host_hdr`
+:enumerator:`TS_CONFIG_WEBSOCKET_ACTIVE_TIMEOUT` :ts:cv:`proxy.config.websocket.active_timeout`
+:enumerator:`TS_CONFIG_WEBSOCKET_NO_ACTIVITY_TIMEOUT` :ts:cv:`proxy.config.websocket.no_activity_timeout`
+:enumerator:`TS_CONFIG_SSL_CLIENT_CERT_FILENAME` :ts:cv:`proxy.config.ssl.client.cert.filename`
+:enumerator:`TS_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME` :ts:cv:`proxy.config.ssl.client.private_key.filename`
+:enumerator:`TS_CONFIG_SSL_CLIENT_CA_CERT_FILENAME` :ts:cv:`proxy.config.ssl.client.CA.cert.filename`
+:enumerator:`TS_CONFIG_SSL_CLIENT_CA_CERT_PATH` :ts:cv:`proxy.config.ssl.client.CA.cert.path`
+:enumerator:`TS_CONFIG_HTTP_HOST_RESOLUTION_PREFERENCE` :ts:cv:`proxy.config.hostdb.ip_resolve`
+:enumerator:`TS_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_INDEX` :ts:cv:`proxy.config.plugin.vc.default_buffer_index`
+:enumerator:`TS_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_WATER_MARK` :ts:cv:`proxy.config.plugin.vc.default_buffer_water_mark`
+:enumerator:`TS_CONFIG_NET_SOCK_NOTSENT_LOWAT` :ts:cv:`proxy.config.net.sock_notsent_lowat`
+:enumerator:`TS_CONFIG_BODY_FACTORY_RESPONSE_SUPPRESSION_MODE` :ts:cv:`proxy.config.body_factory.response_suppression_mode`
+:enumerator:`TS_CONFIG_HTTP_CACHE_POST_METHOD` :ts:cv:`proxy.config.http.cache.post_method`
+:enumerator:`TS_CONFIG_HTTP_CACHE_TARGETED_CACHE_CONTROL_HEADERS` :ts:cv:`proxy.config.http.cache.targeted_cache_control_headers`
+======================================================================== ====================================================================
Examples
========
diff --git a/doc/developer-guide/api/functions/TSHttpSsnClientAddrGet.en.rst b/doc/developer-guide/api/functions/TSHttpSsnClientAddrGet.en.rst
new file mode 100644
index 00000000000..8008f11b0af
--- /dev/null
+++ b/doc/developer-guide/api/functions/TSHttpSsnClientAddrGet.en.rst
@@ -0,0 +1,64 @@
+.. Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed
+ with this work for additional information regarding copyright
+ ownership. The ASF licenses this file to you 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.
+
+
+.. include:: ../../../common.defs
+
+.. default-domain:: cpp
+
+TSHttpSsnClientAddrGet
+**********************
+
+Synopsis
+========
+
+.. code-block:: cpp
+
+ #include
+
+.. function:: struct sockaddr const * TSHttpSsnClientAddrGet(TSHttpSsn ssnp)
+
+Description
+===========
+
+Return the socket address of the client for the HTTP session :arg:`ssnp`.
+The returned pointer references storage owned by |TS| and is only valid
+for the duration of the current callback; plugins that need to keep the
+value across callbacks must copy it into their own storage.
+
+This is the session-level counterpart of :func:`TSHttpTxnClientAddrGet`
+and is appropriate when the caller has a session handle but no specific
+transaction (for example, in session-level hooks).
+
+If the listener that accepted the connection has the ``pp-clnt`` flag set
+and a PROXY Protocol header was successfully parsed, the returned address
+is the PROXY-Protocol source address rather than the immediate TCP peer.
+Without ``pp-clnt`` the returned address is the immediate TCP peer even
+when PROXY Protocol is enabled. See :ref:`Proxy Protocol `
+for the full enumeration of surfaces gated by ``pp-clnt``.
+
+Return Value
+============
+
+A pointer to the client address, or ``nullptr`` if :arg:`ssnp` is invalid
+or no client address is available.
+
+See Also
+========
+
+:manpage:`TSAPI(3ts)`,
+:func:`TSHttpTxnClientAddrGet`,
+:func:`TSNetVConnClientAddrGet`
diff --git a/doc/developer-guide/api/functions/TSHttpTxnCacheKeyDigestGet.en.rst b/doc/developer-guide/api/functions/TSHttpTxnCacheKeyDigestGet.en.rst
new file mode 100644
index 00000000000..28d8ea3fc91
--- /dev/null
+++ b/doc/developer-guide/api/functions/TSHttpTxnCacheKeyDigestGet.en.rst
@@ -0,0 +1,64 @@
+.. Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed
+ with this work for additional information regarding copyright
+ ownership. The ASF licenses this file to you 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.
+
+.. include:: ../../../common.defs
+
+.. default-domain:: cpp
+
+TSHttpTxnCacheKeyDigestGet
+**************************
+
+Synopsis
+========
+
+.. code-block:: cpp
+
+ #include
+
+.. function:: TSReturnCode TSHttpTxnCacheKeyDigestGet(TSHttpTxn txnp, char *buffer, int *length)
+
+Description
+===========
+
+Get the effective cache key digest (cryptographic hash) that was used for
+cache lookup or storage on this transaction. This is the raw hash bytes,
+not a hex or base64 encoding.
+
+The digest size depends on the build configuration: 16 bytes for MD5
+(default) or 32 bytes for SHA-256 (FIPS mode). A 32-byte buffer is
+sufficient for either mode:
+
+.. code-block:: c
+
+ char digest[32];
+ int digest_len = sizeof(digest);
+ if (TSHttpTxnCacheKeyDigestGet(txnp, digest, &digest_len) == TS_SUCCESS) {
+ // digest_len contains the actual number of bytes written
+ }
+
+Pass :code:`nullptr` for *buffer* to query the digest size without
+copying.
+
+Returns :enumerator:`TS_SUCCESS` if a cache key was computed for the
+transaction. Returns :enumerator:`TS_ERROR` if no cache lookup was
+performed, or if *buffer* is non-null and *\*length* is smaller than the
+digest size. In all cases *\*length* is set to the required digest size
+on return.
+
+See Also
+========
+
+:func:`TSHttpTxnCacheLookupUrlGet`
diff --git a/doc/developer-guide/api/functions/TSHttpTxnClientAddrGet.en.rst b/doc/developer-guide/api/functions/TSHttpTxnClientAddrGet.en.rst
new file mode 100644
index 00000000000..c3866bf04d3
--- /dev/null
+++ b/doc/developer-guide/api/functions/TSHttpTxnClientAddrGet.en.rst
@@ -0,0 +1,64 @@
+.. Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed
+ with this work for additional information regarding copyright
+ ownership. The ASF licenses this file to you 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.
+
+
+.. include:: ../../../common.defs
+
+.. default-domain:: cpp
+
+TSHttpTxnClientAddrGet
+**********************
+
+Synopsis
+========
+
+.. code-block:: cpp
+
+ #include
+
+.. function:: struct sockaddr const * TSHttpTxnClientAddrGet(TSHttpTxn txnp)
+
+Description
+===========
+
+Return the socket address of the client that initiated the transaction
+:arg:`txnp`. The returned pointer references storage owned by |TS| and is
+only valid for the duration of the current callback; plugins that need to
+keep the value across callbacks must copy it into their own storage.
+
+The returned ``struct sockaddr`` is address-family agnostic. Inspect the
+``sa_family`` field (or use the ``ats_ip_*`` helpers) to dispatch on IPv4
+versus IPv6.
+
+If the listener that accepted the connection has the ``pp-clnt`` flag set
+and a PROXY Protocol header was successfully parsed, the returned address
+is the PROXY-Protocol source address rather than the immediate TCP peer.
+Without ``pp-clnt`` the returned address is the immediate TCP peer even
+when PROXY Protocol is enabled. See :ref:`Proxy Protocol `
+for the full enumeration of surfaces gated by ``pp-clnt``.
+
+Return Value
+============
+
+A pointer to the client address, or ``nullptr`` if :arg:`txnp` is invalid
+or no client address is available.
+
+See Also
+========
+
+:manpage:`TSAPI(3ts)`,
+:func:`TSHttpSsnClientAddrGet`,
+:func:`TSNetVConnClientAddrGet`
diff --git a/doc/developer-guide/api/functions/TSNetVConnClientAddrGet.en.rst b/doc/developer-guide/api/functions/TSNetVConnClientAddrGet.en.rst
new file mode 100644
index 00000000000..0c69232ddcc
--- /dev/null
+++ b/doc/developer-guide/api/functions/TSNetVConnClientAddrGet.en.rst
@@ -0,0 +1,66 @@
+.. Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed
+ with this work for additional information regarding copyright
+ ownership. The ASF licenses this file to you 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.
+
+
+.. include:: ../../../common.defs
+
+.. default-domain:: cpp
+
+TSNetVConnClientAddrGet
+***********************
+
+Synopsis
+========
+
+.. code-block:: cpp
+
+ #include
+
+.. function:: struct sockaddr const * TSNetVConnClientAddrGet(TSVConn vc)
+
+Description
+===========
+
+Return the socket address of the client for the network virtual connection
+:arg:`vc`. The returned pointer references storage owned by |TS| and is
+only valid for the duration of the current callback; plugins that need to
+keep the value across callbacks must copy it into their own storage.
+
+This is the VConn-level counterpart of :func:`TSHttpTxnClientAddrGet` and
+is appropriate in low-level hooks (for example, SSL or TCP hooks) where
+no HTTP session or transaction is yet associated with the connection.
+
+If the listener that accepted the connection has the ``pp-clnt`` flag set
+and a PROXY Protocol header was successfully parsed, the returned address
+is the PROXY-Protocol source address rather than the immediate TCP peer.
+Without ``pp-clnt`` the returned address is the immediate TCP peer even
+when PROXY Protocol is enabled. Use ``TSNetVConnRemoteAddrGet()`` if the
+immediate TCP peer is always required regardless of ``pp-clnt``. See
+:ref:`Proxy Protocol ` for the full enumeration of
+surfaces gated by ``pp-clnt``.
+
+Return Value
+============
+
+A pointer to the client address, or ``nullptr`` if :arg:`vc` is invalid
+or no client address is available.
+
+See Also
+========
+
+:manpage:`TSAPI(3ts)`,
+:func:`TSHttpTxnClientAddrGet`,
+:func:`TSHttpSsnClientAddrGet`
diff --git a/doc/developer-guide/api/functions/TSUrlHostGet.en.rst b/doc/developer-guide/api/functions/TSUrlHostGet.en.rst
index 1497e625c95..6b7b587fbe3 100644
--- a/doc/developer-guide/api/functions/TSUrlHostGet.en.rst
+++ b/doc/developer-guide/api/functions/TSUrlHostGet.en.rst
@@ -71,6 +71,16 @@ scheme.
:arg:`offset` within the marshal buffer :arg:`bufp`. If there is no explicit
port number in the URL, zero is returned.
+.. note::
+
+ :func:`TSUrlHostGet` operates on a URL object obtained from
+ :func:`TSHttpHdrUrlGet`. In early hooks such as
+ ``TS_HTTP_READ_REQUEST_HDR_HOOK``, the URL object may not yet be fully
+ parsed, and :func:`TSUrlHostGet` may return ``NULL`` even when a ``Host``
+ header is present. For reliable host retrieval at any hook stage, use
+ :func:`TSHttpHdrHostGet` instead, which checks both the URL and the
+ ``Host`` header field.
+
Return Values
=============
@@ -90,6 +100,7 @@ See Also
:manpage:`TSAPI(3ts)`,
:manpage:`TSUrlCreate(3ts)`,
:manpage:`TSHttpHdrUrlGet(3ts)`,
+:manpage:`TSHttpHdrHostGet(3ts)`,
:manpage:`TSUrlHostSet(3ts)`,
:manpage:`TSUrlStringGet(3ts)`,
:manpage:`TSUrlPercentEncode(3ts)`
diff --git a/doc/developer-guide/api/types/TSOverridableConfigKey.en.rst b/doc/developer-guide/api/types/TSOverridableConfigKey.en.rst
index 56d325e6192..4ce7c1712d6 100644
--- a/doc/developer-guide/api/types/TSOverridableConfigKey.en.rst
+++ b/doc/developer-guide/api/types/TSOverridableConfigKey.en.rst
@@ -79,6 +79,7 @@ Enumeration Members
.. enumerator:: TS_CONFIG_HTTP_ORIGIN_MAX_CONNECTIONS
.. enumerator:: TS_CONFIG_HTTP_CONNECT_ATTEMPTS_MAX_RETRIES
.. enumerator:: TS_CONFIG_HTTP_CONNECT_ATTEMPTS_MAX_RETRIES_DOWN_SERVER
+.. enumerator:: TS_CONFIG_HTTP_CONNECT_ATTEMPTS_MAX_RETRIES_SUSPECT_SERVER
.. enumerator:: TS_CONFIG_HTTP_CONNECT_ATTEMPTS_RR_RETRIES
.. enumerator:: TS_CONFIG_HTTP_CONNECT_ATTEMPTS_TIMEOUT
.. enumerator:: TS_CONFIG_HTTP_CONNECT_ATTEMPTS_RETRY_BACKOFF_BASE
@@ -154,6 +155,7 @@ Enumeration Members
.. enumerator:: TS_CONFIG_SSL_CLIENT_SNI_POLICY
.. enumerator:: TS_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME
.. enumerator:: TS_CONFIG_SSL_CLIENT_CA_CERT_FILENAME
+.. enumerator:: TS_CONFIG_SSL_CLIENT_CA_CERT_PATH
.. enumerator:: TS_CONFIG_HTTP_HOST_RESOLUTION_PREFERENCE
.. enumerator:: TS_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_INDEX
.. enumerator:: TS_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_WATER_MARK
diff --git a/doc/developer-guide/config-reload-framework.en.rst b/doc/developer-guide/config-reload-framework.en.rst
index 5b115078940..e6e327d6701 100644
--- a/doc/developer-guide/config-reload-framework.en.rst
+++ b/doc/developer-guide/config-reload-framework.en.rst
@@ -266,8 +266,90 @@ supplied_yaml()
Returns the YAML node supplied via the RPC ``-d`` flag or ``configs`` parameter. If no inline
content was provided, the returned node is undefined (``operator bool()`` returns ``false``).
+ The framework strips the reserved ``_reload`` key from the supplied YAML before delivering it
+ to the handler, so ``supplied_yaml()`` always contains pure config data.
+
+reload_directives()
+ Returns the YAML map extracted from the ``_reload`` key in the RPC-supplied content. If no
+ directives were provided, the returned node is Undefined (``operator bool()`` returns ``false``).
+
+ Directives are operational parameters that modify **how** the handler performs the reload —
+ they are distinct from config **content**. Common uses include scoping a reload to a single
+ entry, enabling a dry-run mode, or passing a version constraint.
+
+ On the wire, directives are nested under ``_reload`` inside the handler's ``configs`` node:
+
+ .. code-block:: json
+
+ {
+ "configs": {
+ "myconfig": {
+ "_reload": { "id": "foo", "dry_run": "true" },
+ "rules": ["rule1", "rule2"]
+ }
+ }
+ }
+
+ The framework extracts ``_reload`` before the handler runs, so:
+
+ - ``reload_directives()`` returns ``{ "id": "foo", "dry_run": "true" }``
+ - ``supplied_yaml()`` returns the remaining content (without ``_reload``)
+ - If ``_reload`` was the only key, ``supplied_yaml()`` is undefined
+
+ Directives and content can coexist. The handler decides how to combine them — the framework
+ delivers both without interpretation.
+
+ **Recommended handler pattern:**
+
+ .. code-block:: cpp
+
+ void MyConfig::reconfigure(ConfigContext ctx) {
+ ctx.in_progress();
+
+ if (auto directives = ctx.reload_directives()) {
+ if (auto id_node = directives["id"]; id_node.IsDefined()) {
+ std::string id = id_node.as();
+ if (!reload_single_entry(id)) {
+ ctx.fail("Unknown entry: " + id);
+ return;
+ }
+ ctx.complete("Reloaded entry: " + id);
+ return;
+ }
+ }
+
+ if (auto yaml = ctx.supplied_yaml()) {
+ if (!load_from_yaml(yaml)) {
+ ctx.fail("Invalid inline content");
+ return;
+ }
+ ctx.complete("Loaded from inline content");
+ return;
+ }
+
+ if (!load_from_file(config_filename)) {
+ ctx.fail("Failed to parse " + config_filename);
+ return;
+ }
+ ctx.complete("Loaded from file");
+ }
+
+ From :program:`traffic_ctl`, directives are passed via ``--directive`` (``-D``):
+
+ .. code-block:: bash
+
+ $ traffic_ctl config reload -D myconfig.id=foo
+
+ See the ``--directive`` option in :ref:`traffic_ctl ` for details.
+
+ .. note::
+
+ Directive values are strings on the wire (the JSONRPC transport serializes all values as
+ double-quoted strings). Handlers use yaml-cpp's ``as()`` to interpret them as needed.
+
add_dependent_ctx(description)
Create a child sub-task. The parent aggregates status from all its children.
+ Child contexts inherit both ``supplied_yaml()`` and ``reload_directives()`` from the parent.
All methods support ``swoc::bwprint`` format strings:
@@ -663,7 +745,7 @@ Logging Best Practices
======================
- Use ``ctx.log()`` for operational messages that appear in
- ``traffic_ctl config status -l`` and :ref:`get_reload_config_status` responses.
+ ``traffic_ctl config status`` and :ref:`get_reload_config_status` responses.
- Use ``ctx.fail(errata, summary)`` when you have a ``swoc::Errata`` with detailed error context.
- Use ``ctx.fail(reason)`` for simple error strings.
- Keep log messages concise — they are stored in memory and included in JSONRPC responses.
@@ -672,6 +754,228 @@ See the :ref:`get_reload_config_status` response examples for how log messages a
task tree output.
+.. _config-reload-unified-macros:
+
+Unified Diagnostic Macros (``CfgLoad*``)
+=========================================
+
+Config handlers often need the same message in two places: the ATS diagnostic log
+(``diags.log`` / ``error.log``) **and** the reload task log (visible via
+:option:`traffic_ctl config status`). The ``CfgLoad*`` macros in
+``mgmt/config/ConfigContextDiags.h`` format the message once and dispatch to both destinations.
+
+Include the header in any source file that uses these macros:
+
+.. code-block:: cpp
+
+ #include "mgmt/config/ConfigContextDiags.h"
+
+Quick Reference
+---------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 15 15 40
+
+ * - Want in diags?
+ - Want in task log?
+ - Use
+ * - Note
+ - yes + in_progress
+ - ``CfgLoadInProgress(ctx, ...)`` (subtasks)
+ * - Note
+ - yes + complete
+ - ``CfgLoadComplete(ctx, ...)``
+ * - Error
+ - yes + fail
+ - ``CfgLoadFail(ctx, ...)``
+ * - Error + Errata
+ - yes + fail
+ - ``CfgLoadFailWithErrata(ctx, errata, ...)``
+ * - Note / Warning
+ - yes (no state change)
+ - ``CfgLoadLog(ctx, DL_Note|DL_Warning, ...)``
+ * - Dbg (conditional on tag)
+ - yes
+ - ``CfgLoadDbg(ctx, ctl, ...)``
+ * - no
+ - yes
+ - ``ctx.log(...)``
+ * - no
+ - yes + state
+ - ``ctx.complete()`` / ``ctx.fail()``
+ * - yes
+ - no
+ - ``Note()`` / ``Warning()`` / ``Error()`` / ``Dbg()`` directly
+
+Macro Details
+-------------
+
+``CfgLoadInProgress(ctx, fmt, ...)``
+ Emits a ``Note()`` to ``diags.log`` and calls ``ctx.in_progress(msg)``. The framework
+ sets ``IN_PROGRESS`` on handler tasks automatically, so this macro is primarily useful
+ for subtasks created via ``add_dependent_ctx()``:
+
+ .. code-block:: cpp
+
+ CfgLoadInProgress(ctx, "%s loading ...", filename);
+
+``CfgLoadComplete(ctx, fmt, ...)``
+ Emits a ``Note()`` to ``diags.log`` and calls ``ctx.complete(msg)``. Use when a config
+ operation finishes successfully:
+
+ .. code-block:: cpp
+
+ CfgLoadComplete(ctx, "%s finished loading", filename);
+
+``CfgLoadFail(ctx, fmt, ...)``
+ Emits an ``Error()`` to ``diags.log`` and the task log, then marks the task as FAIL.
+ Fail always implies ``DL_Error`` — if the condition is merely degraded (not fatal to
+ the load), use ``CfgLoadLog(ctx, DL_Warning, ...)`` + ``CfgLoadComplete()`` instead:
+
+ .. code-block:: cpp
+
+ CfgLoadFail(ctx, "%s failed to load", filename);
+
+``CfgLoadFailWithErrata(ctx, errata, fmt, ...)``
+ Like ``CfgLoadFail`` but also appends ``swoc::Errata`` annotations to the task log.
+ Combines ``CfgLoadFail`` + ``ctx.fail(errata)`` in one call — see
+ :ref:`config-reload-errata-handling` below.
+
+``CfgLoadLog(ctx, level, fmt, ...)``
+ Emits at the given ``DiagsLevel`` and calls ``ctx.log(level, msg)`` **without changing
+ task state**. Use for intermediate informational messages:
+
+ .. code-block:: cpp
+
+ CfgLoadLog(ctx, DL_Warning, "ControlMatcher - Cannot open config file: %s", path);
+ CfgLoadLog(ctx, DL_Note, "loaded %d categories from %s", count, filename);
+
+``CfgLoadDbg(ctx, dbg_ctl, fmt, ...)``
+ Emits via ``Dbg()`` (conditional on the tag being enabled) and always adds to the task log
+ at ``DL_Debug``. Use for debug-level messages that should also appear in reload status:
+
+ .. code-block:: cpp
+
+ CfgLoadDbg(ctx, dbg_ctl_ssl, "Reload SNI file");
+
+.. _config-reload-errata-handling:
+
+Errata Handling
+---------------
+
+For failures with ``swoc::Errata`` detail, use ``CfgLoadFailWithErrata`` to combine
+the diags summary, errata detail, and state change in a single call:
+
+.. code-block:: cpp
+
+ CfgLoadFailWithErrata(ctx, errata, "%s failed to load", filename);
+
+This logs the formatted message to ``diags.log`` at ``DL_Error``, appends it to
+the task log, then calls ``ctx.fail(errata)`` which stores each errata annotation
+(with its own severity) in the task log and marks the task as FAIL.
+
+For errors that should not change state, pair ``CfgLoadLog`` with ``ctx.log(errata)``:
+
+.. code-block:: cpp
+
+ CfgLoadLog(ctx, DL_Error, "Cannot open %s", path);
+ ctx.log(errata); // errata detail -> task log only
+
+When NOT to Use Macros
+-----------------------
+
+- **Task-log-only messages** — use ``ctx.log()`` directly when the message is only useful in
+ ``traffic_ctl`` output and should not appear in ``diags.log``.
+- **State-only transitions** — use ``ctx.in_progress()`` / ``ctx.complete()`` / ``ctx.fail()``
+ directly when there is no message to emit to ``diags.log``.
+- **Fatal errors** — ``Fatal()`` terminates the process; reload status is irrelevant.
+ Call ``Fatal()`` directly.
+
+
+Severity-Aware Task Logs
+=========================
+
+Each task log entry carries a ``DiagsLevel`` severity. State-transition methods carry implicit
+severity: ``in_progress(text)`` and ``complete(text)`` store ``DL_Note``, ``fail(text)`` stores
+``DL_Error``. The ``CfgLoad*`` macros and ``ctx.log(level, text)`` store the caller-specified
+level. Only the one-argument ``ctx.log(text)`` (no level) stores ``DL_Undefined`` — these
+entries are always shown regardless of ``--min-level`` filtering.
+
+In :option:`traffic_ctl config status` output, entries with a defined severity are prefixed
+with a tag:
+
+.. code-block:: text
+
+ ✗ ssl_client_coordinator ······················· 2ms ✗ FAIL
+ │ [Note] SSL configs reloaded
+ ├─ ✔ SSLConfig ································· 1ms
+ │ [Note] SSLConfig loading ...
+ │ [Note] SSLConfig reloaded
+ ├─ ✗ SNIConfig ································· 1ms ✗ FAIL
+ │ [Note] sni.yaml loading ...
+ │ [Err] sni.yaml failed to load
+ └─ ✔ SSLCertificateConfig ······················ 0ms
+ [Note] (ssl) ssl_multicert.yaml loading ...
+ [Warn] Cannot open SSL certificate configuration "ssl_multicert.yaml" - No such file or directory
+ [Note] (ssl) ssl_multicert.yaml finished loading
+
+The ``--min-level`` option on :option:`traffic_ctl config status` filters log entries
+by severity — see :option:`traffic_ctl config status` for details.
+
+The severity is also available in JSON output (``--format json``) as an integer ``level``
+field on each log entry, where the value maps to the ``DiagsLevel`` enum (e.g. ``1`` = Debug,
+``3`` = Note, ``4`` = Warning, ``5`` = Error).
+
+
+.. _config-reload-diags-log:
+
+Reload Summary in ``diags.log``
+================================
+
+After a reload reaches a terminal state (confirmed after a 5-second grace period), a summary
+line is logged to ``diags.log``:
+
+**Success:**
+
+.. code-block:: text
+
+ NOTE: Config reload [my-token] completed: 3/3 tasks succeeded
+
+**Failure:**
+
+.. code-block:: text
+
+ WARNING: Config reload [my-token] finished with failures: 1 succeeded, 1 failed (3 total) — run: traffic_ctl config status -t my-token
+
+When the ``config.reload`` debug tag is enabled, a detailed dump of all subtasks and their
+log entries is written to ``traffic.out`` / ``diags.log``:
+
+.. code-block:: text
+
+ DIAG: (config.reload) [fail] ssl_client_coordinator
+ DIAG: (config.reload) [Note] SSL configs reloaded
+ DIAG: (config.reload) [success] SSLConfig
+ DIAG: (config.reload) [Note] SSLConfig loading ...
+ DIAG: (config.reload) [Note] SSLConfig reloaded
+ DIAG: (config.reload) [fail] SNIConfig
+ DIAG: (config.reload) [Note] sni.yaml loading ...
+ DIAG: (config.reload) [Err] sni.yaml failed to load
+ DIAG: (config.reload) [success] ssl_ticket_key
+ DIAG: (config.reload) [Note] SSL ticket key loading ...
+ DIAG: (config.reload) [Note] SSL ticket key reloaded
+
+Enable this tag for troubleshooting:
+
+.. code-block:: yaml
+
+ records:
+ diags:
+ debug:
+ enabled: 1
+ tags: config.reload
+
+
Testing
========
@@ -683,11 +987,20 @@ After registering a new handler:
3. Run :option:`traffic_ctl config status` to verify the handler appears in the task tree with
the correct status.
4. Introduce a parse error in the config file and reload — verify the handler reports ``FAIL``.
-5. Use :option:`traffic_ctl config status` ``--format json`` to inspect the raw
+5. Check that severity tags (``[Dbg]``, ``[Err]``, etc.) appear correctly in
+ :option:`traffic_ctl config status` output and that ``--min-level`` filtering works.
+6. Enable the ``config.reload`` debug tag and verify the detailed dump appears in ``diags.log``.
+7. Use :option:`traffic_ctl config status` ``--format json`` to inspect the raw
:ref:`get_reload_config_status` response for automation testing.
-**Autests** — the project includes autest helpers for config reload testing. Use
-``AddJsonRPCClientRequest`` with ``Request.admin_config_reload()`` to trigger reloads, and
+**Autests** — the project includes autest helpers for config reload testing.
+
+For **end-to-end tests** that trigger a reload via ``traffic_ctl`` and validate the result, use
+the :ref:`autest-config-reload` extension (``Test.AddConfigReload()``).
+This is the recommended approach for most reload tests.
+
+For **JSONRPC-level tests** that need fine-grained control over request and response payloads,
+use ``AddJsonRPCClientRequest`` with ``Request.admin_config_reload()`` to trigger reloads, and
``Testers.CustomJSONRPCResponse`` to validate responses programmatically. See the existing tests
for examples:
diff --git a/doc/developer-guide/core-architecture/hostdb.en.rst b/doc/developer-guide/core-architecture/hostdb.en.rst
index 18a0e06e61d..0666c8bfb28 100644
--- a/doc/developer-guide/core-architecture/hostdb.en.rst
+++ b/doc/developer-guide/core-architecture/hostdb.en.rst
@@ -50,12 +50,10 @@ a flag, where a value of ``TS_TIME_ZERO`` indicates a live target and any other
down info.
If an info is marked down (has a non-zero last failure time) there is a "fail window" during which
-no connections are permitted. After this time the info is considered to be a "zombie". If all infos
+no connections are permitted. After this time the info is considered to be a "suspect". If all infos
for a record are down then a specific error message is generated (body factory tag
-"connect#all_down"). Otherwise if the selected info is a zombie, a request is permitted but the
-zombie is immediately marked down again, preventing any additional requests until either the fail
-window has passed or the single connection succeeds. A successful connection clears the last file
-time and the info becomes alive.
+"connect#all_down"). Otherwise if the selected info is a suspect, connections are permitted and the
+info will transition back to up on success or down on failure.
Runtime Structure
=================
@@ -152,8 +150,8 @@ Future
There is still some work to be done in future PRs.
-* The fail window and the zombie window should be separate values. It is quite reasonable to want
- to configure a very short fail window (possibly 0) with a moderately long zombie window so that
+* The fail window and the suspect window should be separate values. It is quite reasonable to want
+ to configure a very short fail window (possibly 0) with a moderately long suspect window so that
probing connections can immediately start going upstream at a low rate.
* Failing an upstream should be more loosely connected to transactions. Currently there is a one
@@ -189,7 +187,7 @@ This version has several major architectural changes from the previous version.
* State information has been promoted to atomics and updates are immediate rather than scheduled.
This also means the data in the state machine is a reference to a shared object, not a local copy.
- The promotion was necessary to coordinate zombie connections to upstreams marked down across transactions.
+ The promotion was necessary to coordinate suspect connections to upstreams marked down across transactions.
* The "resolve key" is now a separate data object from the HTTP request. This is a subtle but
major change. The effect is requests can be routed to different upstreams without changing
diff --git a/doc/developer-guide/logging-architecture/binary-log-v3-format.en.rst b/doc/developer-guide/logging-architecture/binary-log-v3-format.en.rst
new file mode 100644
index 00000000000..7deb532ae14
--- /dev/null
+++ b/doc/developer-guide/logging-architecture/binary-log-v3-format.en.rst
@@ -0,0 +1,218 @@
+.. Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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.
+
+.. include:: ../../common.defs
+
+.. _binary-log-v3-format:
+
+Self-Describing Binary Log Format (v3)
+**************************************
+
+This page specifies the on-disk format of a binary log segment, version 3, in
+enough detail to implement a decoder *without* the Traffic Server source tree.
+A version 3 segment is **self-describing**: every field's type is published in
+the segment header, so a generic reader can decode each entry by dispatching on
+a small, stable set of type codes — no embedded copy of the ATS symbol-to-type
+table is required.
+
+Motivation
+==========
+
+In version 2, a segment header carries the field *symbols* (``fmt_fieldlist``,
+e.g. ``"chi,cqu,pssc"``) and a printf-style *template* (``fmt_printf``) but
+**not** the field types. To decode an entry a reader had to already know the
+type of each symbol, because the value encodings are only self-delimiting once
+the type is known (``IP`` is variable length, for example). That coupled every
+out-of-tree parser to the exact ATS build that wrote the log.
+
+Version 3 adds one thing: a per-segment **field-type schema** that lists the
+wire type of every field, in field order. Decoding then needs only the symbols
+(as keys) and the schema (for types).
+
+Segment layout
+==============
+
+A ``.blog`` file is a stream of segments, each a serialized ``LogBuffer``:
+
+::
+
+ LogBufferHeader (per segment)
+ cookie = 0xaceface
+ version = 3
+ format_type, byte_count, entry_count, timestamps, flags, signature
+ fmt_name_offset
+ fmt_fieldlist_offset -> "chi,cqu,pssc,..." (symbols, comma separated)
+ fmt_printf_offset -> "% % ..."
+ src_hostname_offset, log_filename_offset
+ data_offset -> first entry
+ fmt_fieldtypes_offset -> field-type schema (NEW in v3)
+ [ LogEntryHeader | field0 field1 field2 ... ] x entry_count
+ LogEntryHeader: timestamp(8) timestamp_usec(4) entry_len(4)
+ fields: concatenated in fieldlist order, no per-field tags
+
+All ``*_offset`` members are byte offsets from the start of the segment (the
+address of the ``LogBufferHeader``). ``fmt_fieldtypes_offset`` is appended
+**after** ``data_offset`` so that the layout through ``data_offset`` is
+byte-identical to version 2; a value of ``0`` means the schema is absent (e.g.
+a text-format segment, or a version 2 segment).
+
+Field-type schema
+=================
+
+At ``fmt_fieldtypes_offset`` the segment stores:
+
+::
+
+ uint16_t field_count; // == number of symbols in fmt_fieldlist
+ uint8_t type_code[field_count]; // one type code per field, in order
+
+``type_code[i]`` is the type of the i-th field, which corresponds to the i-th
+symbol in ``fmt_fieldlist`` and the i-th value in each entry. The ``uint16_t``
+``field_count`` prefix is written in **host byte order**, like the rest of
+``LogBufferHeader``. The blob is padded along with the header to an 8-byte
+boundary.
+
+The schema carries no independent version of its own: the segment ``version``
+(``3`` here) governs this layout, so a future schema change rides the same
+``LOG_SEGMENT_VERSION`` bump rather than a second, separate counter.
+
+Stable type codes
+=================
+
+The type codes are the values of the in-tree ``LogField::Type`` enumeration,
+serialized directly. They are part of the published format and are
+**append-only**: codes are never renumbered or reused.
+
+==== ========= ===========================================================
+Code Name Wire encoding
+==== ========= ===========================================================
+0 INVALID Reserved. Not emitted by a correct writer; a reader that
+ meets it -- or any code it does not recognize -- cannot
+ determine the field length and must stop decoding the entry.
+1 sINT A single ``int64_t``, fixed 8 bytes, **host byte order**.
+2 dINT Two ``int64_t`` (16 bytes), host byte order. Used for
+ values stored as two integers, e.g. HTTP version
+ major/minor.
+3 STRING NUL-terminated bytes, then padded to an 8-byte boundary.
+4 IP ``uint16_t`` address family followed by a family-sized
+ address, then padded to an 8-byte boundary (see below).
+==== ========= ===========================================================
+
+The code reflects how the value is *framed* on disk, i.e. how a reader walks
+(or skips) it -- not what the value means. (The ``sINT``/``dINT`` names are an
+ATS-internal distinction; on the wire ``sINT`` is one 8-byte integer and
+``dINT`` is two consecutive ones.) How a consumer *renders* a value -- mapping
+a cache-result integer to ``TCP_HIT``, or a ``dINT`` to ``1.1`` -- is layered
+on top by the consumer and is not part of the wire format.
+
+Value encodings
+===============
+
+sINT
+ An ``int64_t`` occupying exactly 8 bytes, in **host byte order** (as in
+ version 2). Integer values are not endianness-normalized, so a ``.blog`` is
+ not portable across hosts of differing endianness; cross-architecture
+ portability is future work.
+
+dINT
+ Two consecutive ``sINT`` values: 16 bytes total, in host byte order. Used
+ where one log field is stored as two integers, such as an HTTP version
+ (major then minor). The reference decoder renders it as a JSON array, e.g.
+ ``[1,1]``; turning that into ``1.1`` is a consumer concern.
+
+STRING
+ The string bytes followed by a single NUL, then zero padding up to the next
+ 8-byte boundary. The on-wire length is therefore
+ ``align_up(strlen + 1, 8)``. An empty/absent string is written as ``"-"``.
+
+IP
+ A ``uint16_t`` address family in host byte order, then:
+
+ .. list-table::
+ :header-rows: 1
+ :widths: 30 70
+
+ * - Family
+ - Following bytes
+ * - ``AF_INET`` (IPv4)
+ - 4-byte ``in_addr``
+ * - ``AF_INET6`` (IPv6)
+ - 16-byte ``in6_addr``
+ * - ``AF_UNIX``
+ - fixed-size path buffer
+ * - ``AF_UNSPEC`` / other
+ - no address bytes
+
+ The whole field is padded to the next 8-byte boundary. Because the length
+ depends on the family byte *inside* the value, only a reader that knows the
+ field is an ``IP`` (from the schema) can compute its size — which is exactly
+ why the schema is required to skip or decode unknown fields safely.
+
+Decoding an entry
+=================
+
+Given a segment, a generic decoder:
+
+#. Reads ``field_count`` and the ``type_code[]`` array from the schema at
+ ``fmt_fieldtypes_offset``.
+#. Splits ``fmt_fieldlist`` into ``field_count`` comma-separated symbols
+ (``LogFormat::parse_format_string()`` joins symbols with ``,``; the reference
+ decoder also tolerates spaces as separators).
+#. For each entry (located via ``data_offset`` and walked using
+ ``LogEntryHeader::entry_len``), reads the fields left to right, using
+ ``type_code[i]`` to pick the encoding above and advance the read cursor.
+
+The reference implementation is ``log_entry_to_json()``
+(``src/traffic_logcat/LogEntryJson.cc``), which renders an entry as a JSON
+object using only the symbols and the schema — it does not consult the global
+field table. It is exposed by :program:`traffic_logcat`'s ``-j``/``--json``
+option. For example, a three-field entry decodes to:
+
+::
+
+ {"chi":"192.0.2.10","cqu":"GET /index.html","pssc":200}
+
+.. note::
+
+ Some integer fields hold coded values (cache result, hierarchy, finish
+ status, etc.). The binary format stores the raw integer; mapping it to a
+ mnemonic such as ``TCP_HIT`` is a presentation concern left to the consumer.
+
+Compatibility
+=============
+
+* **New reader, old file (v3 reader, v2 file):** supported. The readers shipped
+ with Traffic Server accept the inclusive version range
+ ``[2, 3]`` and size the header read to the on-disk version, so a v2 segment
+ (which has no ``fmt_fieldtypes_offset``) still decodes. Its ASCII output is
+ produced from ``fmt_fieldlist`` + ``fmt_printf`` exactly as before.
+* **Old reader, new file (v2 reader, v3 file):** a reader built before v3
+ support gates on the version and will skip v3 segments. v3 logs therefore
+ require tooling from a release that understands v3. As an escape hatch, a
+ binary log object can be pinned to the version 2 layout with
+ ``binary_log_version: 2`` in :file:`logging.yaml`, so a not-yet-upgraded
+ downstream parser keeps working during a migration.
+* The text/Squid/CLF ASCII output paths are unchanged: the schema is additive
+ and ignored when rendering ASCII.
+
+.. note::
+
+ v3 does not change integer endianness: field values, the integers in
+ ``LogBufferHeader`` / ``LogEntryHeader``, and the ``IP`` family word are all
+ written in host byte order, as in v2. A ``.blog`` is therefore not portable
+ across hosts of differing endianness; cross-architecture portability is
+ future work.
diff --git a/doc/developer-guide/logging-architecture/index.en.rst b/doc/developer-guide/logging-architecture/index.en.rst
index 16b6fd8bbdf..542e05ad5fa 100644
--- a/doc/developer-guide/logging-architecture/index.en.rst
+++ b/doc/developer-guide/logging-architecture/index.en.rst
@@ -26,3 +26,4 @@ Logging Architecture
:maxdepth: 2
architecture.en
+ binary-log-v3-format.en
diff --git a/doc/developer-guide/release-process/index.en.rst b/doc/developer-guide/release-process/index.en.rst
index 47437dd3163..10526aa2e02 100644
--- a/doc/developer-guide/release-process/index.en.rst
+++ b/doc/developer-guide/release-process/index.en.rst
@@ -67,8 +67,8 @@ Build
#. Generate or update the CHANGELOG for the next release. ::
- ./tools/git/changelog.pl -o apache -r trafficserver -m X.Y.Z >
- CHANGELOG-X.Y.Z
+ uv run --project tools/changelog python tools/changelog/changelog.py \
+ -o apache -r trafficserver -m X.Y.Z --use-gh > CHANGELOG-X.Y.Z
#. Commit this file to the repository and push it to the release branch.
diff --git a/doc/developer-guide/testing/config-reload-ext.en.rst b/doc/developer-guide/testing/config-reload-ext.en.rst
new file mode 100644
index 00000000000..92e5ba15705
--- /dev/null
+++ b/doc/developer-guide/testing/config-reload-ext.en.rst
@@ -0,0 +1,259 @@
+.. Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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.
+
+.. include:: ../../common.defs
+
+.. _autest-config-reload:
+
+Config Reload Test Extension
+****************************
+
+The ``config_reload.test.ext`` extension provides ``Test.AddConfigReload()`` to
+replace the legacy pattern of fire-and-forget ``traffic_ctl config reload``
+followed by log grepping with a deterministic, structured approach.
+
+The extension is loaded automatically from
+``tests/gold_tests/autest-site/config_reload.test.ext``.
+
+
+Why Use This Extension
+======================
+
+The legacy reload test pattern is fragile:
+
+.. code-block:: python
+
+ # OLD: fire-and-forget + sleep + log grep
+ tr = Test.AddTestRun("Reload config")
+ p = tr.Processes.Process("reload-1")
+ p.Command = 'traffic_ctl config reload; sleep 30'
+ p.Env = ts.Env
+ p.ReturnCode = Any(0, -2)
+ p.Ready = When.FileContains(
+ ts.Disk.diags_log.Name, "finished loading", 2)
+ p.Timeout = 20
+ tr.Processes.Default.StartBefore(p)
+ tr.Processes.Default.Command = 'echo "waiting for reload"'
+ tr.TimeOut = 25
+
+Problems with this approach:
+
+- Relies on exact log text that can change across versions.
+- Uses ``sleep`` for synchronization, leading to slow and flaky tests.
+- Does not validate *which* config handler ran.
+- Does not detect reload failures.
+
+The new pattern is a single call:
+
+.. code-block:: python
+
+ # NEW: deterministic, validates specific handlers
+ tr = Test.AddConfigReload(ts, expect_tasks=["sni.yaml"],
+ description="Reload after sni.yaml touch")
+
+
+How It Works
+============
+
+``AddConfigReload`` uses ``traffic_ctl config reload -m`` (monitor mode) to
+trigger a reload and **block until it completes**. Monitor mode polls the server
+for the reload status, so there is no sleeping or guessing. The default timeout
+is **30 seconds** (configurable via the ``timeout`` parameter).
+
+When ``expect_tasks`` or ``expect_absent_tasks`` is set, a second test run
+queries the ``get_reload_config_status`` JSONRPC endpoint and validates the task
+tree via ``CustomJSONRPCResponse``. This gives tests access to the full
+structured result — including per-task status, subtasks, and descriptions —
+without relying on the human-readable output of ``traffic_ctl``.
+
+When neither ``expect_tasks`` nor ``expect_absent_tasks`` is set, only the exit
+code is validated (no JSONRPC query). This is useful for reloads where you only
+care that the reload succeeded (exit code 0).
+
+
+Test.AddConfigReload
+====================
+
+Triggers a config reload, blocks until completion, and validates the result.
+
+.. code-block:: python
+
+ tr = Test.AddConfigReload(
+ ts, # ATS process object
+ expect="success", # "success", "fail", "timeout", or "any"
+ token=None, # custom token (auto-generated if None)
+ data=None, # inline YAML or @file path
+ force=False, # --force flag
+ timeout="30s", # monitor timeout
+ initial_wait=1.0, # seconds before first poll
+ refresh_int=0.5, # seconds between polls
+ expect_tasks=None, # list or dict of expected handler names
+ expect_absent_tasks=None, # list of handler names that must NOT appear
+ description=None, # test run description (recommended)
+ )
+
+Parameters
+----------
+
+``ts``
+ The ATS process object (from ``Test.MakeATSProcess()``).
+
+``expect``
+ Expected outcome:
+
+ - ``"success"`` — exit code 0 (all handlers succeeded)
+ - ``"fail"`` — exit code 2 (one or more handlers failed)
+ - ``"timeout"`` — exit code 75 (monitor timed out)
+ - ``"any"`` — exit code 0 or 2 (don't care about outcome)
+
+ Default: ``"success"``.
+
+``token``
+ A custom reload token string. If ``None``, an auto-generated token
+ (``autest-reload-1``, ``autest-reload-2``, ...) is used. Tokens are unique
+ per test file.
+
+``data``
+ Inline YAML content or a ``@file`` path to pass via ``--data``. When the
+ value starts with ``@``, it is passed as-is (e.g. ``@/path/to/file.yaml``).
+ Otherwise the string is shell-quoted and passed inline.
+
+ .. note::
+
+ The ``--data`` flag is accepted by ``traffic_ctl config reload`` but
+ individual reload handlers do not yet consume inline data. This parameter
+ is reserved for future use.
+
+``force``
+ If ``True``, adds the ``--force`` flag to start a new reload even when one
+ is already in progress. See the ``traffic_ctl config reload`` documentation
+ for details on force behavior.
+
+``timeout``
+ Duration string for the monitor timeout (e.g. ``"30s"``, ``"1m"``). This
+ controls how long ``traffic_ctl config reload -m`` will poll before giving
+ up. Default: ``"30s"``. Set to ``None`` to disable the timeout (not
+ recommended).
+
+``initial_wait``
+ Seconds to wait before the first poll, giving the server time to schedule
+ handlers. Default: ``1.0``.
+
+``refresh_int``
+ Seconds between status polls. Default: ``0.5``.
+
+``expect_tasks``
+ Expected handler/config names in the reload. Accepts two forms:
+
+ - **List** — checks that each name appears somewhere in the task tree:
+
+ .. code-block:: python
+
+ expect_tasks=["ip_allow.yaml", "sni.yaml"]
+
+ - **Dict** — checks presence *and* per-task status:
+
+ .. code-block:: python
+
+ expect_tasks={"sni.yaml": "fail", "SSLConfig": "success"}
+
+ When not set (``None``), no JSONRPC validation is performed — only the exit
+ code is checked.
+
+``expect_absent_tasks``
+ A list of handler/config names that must **not** appear in the reload task
+ tree. Useful for verifying that touching an unrelated file did not trigger
+ a specific handler.
+
+``description``
+ Description for the ``TestRun``. **Recommended** — always pass a description
+ for readable test output. When omitted, an auto-generated description is
+ used (e.g. ``"Reload config [autest-reload-1]"``).
+
+Return Value
+------------
+
+Returns the reload ``TestRun`` object (the first test run). Callers can add
+extra assertions or ``StillRunningAfter`` references:
+
+.. code-block:: python
+
+ tr = Test.AddConfigReload(ts, expect_tasks=["remap.config"],
+ description="Reload after remap.config edit")
+ tr.StillRunningAfter = ts
+ tr.StillRunningAfter = origin_server
+
+
+.. note::
+
+ Standalone record-triggered reloads (via ``traffic_ctl config set`` without
+ an explicit ``config reload``) do not create tasks in the reload framework
+ and cannot be verified with this extension.
+
+
+Examples
+========
+
+Basic reload after touching a config file:
+
+.. code-block:: python
+
+ tr = Test.AddTestRun("Touch ip_allow.yaml")
+ tr.Processes.Default.Command = f"touch {config_dir}/ip_allow.yaml"
+ tr.Processes.Default.ReturnCode = 0
+ tr.StillRunningAfter = ts
+
+ tr = Test.AddConfigReload(ts, expect_tasks=["ip_allow.yaml"],
+ description="Reload after ip_allow.yaml touch")
+
+Expecting a reload failure (e.g. broken sni.yaml):
+
+.. code-block:: python
+
+ tr = Test.AddConfigReload(ts, expect="fail", expect_tasks=["sni.yaml"],
+ description="Reload with broken sni.yaml")
+
+Verifying a handler was NOT triggered:
+
+.. code-block:: python
+
+ tr = Test.AddConfigReload(ts, expect_absent_tasks=["ip_allow.yaml"],
+ description="Reload (should NOT trigger ip_allow)")
+
+Per-task status validation:
+
+.. code-block:: python
+
+ tr = Test.AddConfigReload(
+ ts,
+ expect="fail",
+ expect_tasks={"sni.yaml": "fail", "SSLConfig": "success"},
+ description="Reload with mixed task outcomes",
+ )
+
+Reload with inline YAML data:
+
+.. code-block:: python
+
+ # NOTE: --data is accepted by traffic_ctl but individual reload handlers
+ # do not yet consume inline data. Reserved for future use.
+ tr = Test.AddConfigReload(
+ ts,
+ data="ip_allow:\n - apply: in\n ip_addrs: 0/0\n action: allow",
+ expect_tasks=["ip_allow.yaml"],
+ description="Reload with inline ip_allow data",
+ )
diff --git a/doc/developer-guide/testing/index.en.rst b/doc/developer-guide/testing/index.en.rst
index 363dabc4e55..d14c2542e5f 100644
--- a/doc/developer-guide/testing/index.en.rst
+++ b/doc/developer-guide/testing/index.en.rst
@@ -26,3 +26,4 @@ Testing Traffic Server
:maxdepth: 2
autests.en
+ config-reload-ext.en
diff --git a/doc/release-notes/upgrading.en.rst b/doc/release-notes/upgrading.en.rst
index 4fde3d3c7fa..0548c19b721 100644
--- a/doc/release-notes/upgrading.en.rst
+++ b/doc/release-notes/upgrading.en.rst
@@ -146,6 +146,11 @@ The following :file:`records.yaml` changes have been made:
- The records.yaml entry ``proxy.config.http.down_server.abort_threshold`` has been removed.
- The records.yaml entry ``proxy.config.http.connect_attempts_max_retries_dead_server`` has been renamed to :ts:cv:`proxy.config.http.connect_attempts_max_retries_down_server`.
+- The records.yaml entry ``proxy.config.http.connect_attempts_max_retries_down_server`` is now deprecated in favor of
+ :ts:cv:`proxy.config.http.connect_attempts_max_retries_suspect_server`. The new name aligns with the
+ ``HostDBInfo::State::SUSPECT`` state it actually applies to (a recovering origin allowed a limited probe budget after
+ :ts:cv:`proxy.config.http.down_server.cache_time` elapses). When only the deprecated record is set, its value is mirrored
+ forward to the new record and a warning is logged. When both are set, the new record wins.
- The entry ``proxy.config.http.connect.dead.policy`` has been renamed to :ts:cv:`proxy.config.http.connect.down.policy`.
- The records.yaml entry ``proxy.config.http.parent_proxy.connect_attempts_timeout`` and
``proxy.config.http.post_connect_attempts_timeout`` have been removed. Instead use
@@ -163,6 +168,10 @@ The following :file:`records.yaml` changes have been made:
:ts:cv:`proxy.config.http.header_field_max_size` have been changed to 32KB.
- The records.yaml entry :ts:cv:`proxy.config.http.server_ports` now also accepts the
``allow-plain`` option
+- The records.yaml entry :ts:cv:`proxy.config.http.proxy_protocol_allowlist` is now enforced
+ only for connections on Proxy Protocol-enabled ports that begin with a Proxy Protocol
+ header preface. Non-Proxy Protocol traffic on flexible Proxy Protocol ports is no longer
+ restricted by this setting; use :file:`ip_allow.yaml` for general source-IP access control.
- The records.yaml entry :ts:cv:`proxy.config.http.cache.max_open_write_retry_timeout` has been added to specify a timeout for starting a write to cache
- The records.yaml entry :ts:cv:`proxy.config.net.per_client.max_connections_in` has
been added to limit the number of connections from a client IP. This works the
diff --git a/example/plugins/c-api/CMakeLists.txt b/example/plugins/c-api/CMakeLists.txt
index 65594cd391e..ea40f39ceac 100644
--- a/example/plugins/c-api/CMakeLists.txt
+++ b/example/plugins/c-api/CMakeLists.txt
@@ -24,6 +24,7 @@ add_atsplugin(secure_link ./secure_link/secure_link.cc)
target_link_libraries(secure_link PRIVATE OpenSSL::SSL)
add_atsplugin(remap ./remap/remap.cc)
add_atsplugin(redirect_1 ./redirect_1/redirect_1.cc)
+add_atsplugin(redo_cache_lookup ./redo_cache_lookup/redo_cache_lookup.cc)
add_atsplugin(query_remap ./query_remap/query_remap.cc)
add_atsplugin(thread_pool ./thread_pool/psi.cc ./thread_pool/thread.cc)
add_atsplugin(bnull_transform ./bnull_transform/bnull_transform.cc)
@@ -66,3 +67,10 @@ add_atsplugin(protocol_stack ./protocol_stack/protocol_stack.cc)
add_atsplugin(client_context_dump ./client_context_dump/client_context_dump.cc)
target_link_libraries(client_context_dump PRIVATE OpenSSL::SSL libswoc::libswoc)
add_atsplugin(custom_logfield ./custom_logfield/custom_logfield.cc)
+
+if(BUILD_TESTING)
+ add_executable(test_redo_cache_lookup ./redo_cache_lookup/unit_tests/test_redo_cache_lookup_config.cc)
+ target_include_directories(test_redo_cache_lookup PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/redo_cache_lookup)
+ target_link_libraries(test_redo_cache_lookup PRIVATE Catch2::Catch2WithMain)
+ add_catch2_test(NAME test_redo_cache_lookup COMMAND test_redo_cache_lookup)
+endif()
diff --git a/example/plugins/c-api/redirect_1/redirect_1.cc b/example/plugins/c-api/redirect_1/redirect_1.cc
index 701446a5661..6a9a9a71fa6 100644
--- a/example/plugins/c-api/redirect_1/redirect_1.cc
+++ b/example/plugins/c-api/redirect_1/redirect_1.cc
@@ -97,7 +97,7 @@ static void
handle_client_lookup(TSHttpTxn txnp, TSCont contp)
{
TSMBuffer bufp;
- TSMLoc hdr_loc, url_loc;
+ TSMLoc hdr_loc;
int host_length;
in_addr_t clientip = 0;
@@ -130,16 +130,9 @@ handle_client_lookup(TSHttpTxn txnp, TSCont contp)
goto done;
}
- if (TSHttpHdrUrlGet(bufp, hdr_loc, &url_loc) != TS_SUCCESS) {
- TSError("[%s] Couldn't retrieve request url", PLUGIN_NAME);
- TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
- goto done;
- }
-
- host = TSUrlHostGet(bufp, url_loc, &host_length);
+ host = TSHttpHdrHostGet(bufp, hdr_loc, &host_length);
if (!host) {
TSError("[%s] Couldn't retrieve request hostname", PLUGIN_NAME);
- TSHandleMLocRelease(bufp, hdr_loc, url_loc);
TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
goto done;
}
@@ -148,7 +141,6 @@ handle_client_lookup(TSHttpTxn txnp, TSCont contp)
* Check to see if the client is already headed to the redirect site.
*/
if (strncmp(host, url_redirect, host_length) == 0) {
- TSHandleMLocRelease(bufp, hdr_loc, url_loc);
TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
goto done;
}
@@ -159,7 +151,6 @@ handle_client_lookup(TSHttpTxn txnp, TSCont contp)
update_redirected_method_stats(bufp, hdr_loc);
- TSHandleMLocRelease(bufp, hdr_loc, url_loc);
TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
/*
diff --git a/example/plugins/c-api/redo_cache_lookup/readme.txt b/example/plugins/c-api/redo_cache_lookup/readme.txt
new file mode 100644
index 00000000000..9f3008400f3
--- /dev/null
+++ b/example/plugins/c-api/redo_cache_lookup/readme.txt
@@ -0,0 +1,13 @@
+# Redo Cache Lookup Example Plugin
+
+This plugin shows how to use the `TSHttpTxnRedoCacheLookup` C API. It
+checks cache lookup results and asks ATS to retry the lookup with a fallback
+URL when the original lookup misses or is skipped.
+
+## Configuration
+
+Add this plugin to `plugin.config` with the `--fallback` option:
+
+```
+redo_cache_lookup.so --fallback http://example.com/fallback_url
+```
diff --git a/example/plugins/c-api/redo_cache_lookup/redo_cache_lookup.cc b/example/plugins/c-api/redo_cache_lookup/redo_cache_lookup.cc
new file mode 100644
index 00000000000..c370fae0e22
--- /dev/null
+++ b/example/plugins/c-api/redo_cache_lookup/redo_cache_lookup.cc
@@ -0,0 +1,91 @@
+/** @file
+
+ An example plugin to redo cache lookups with a fallback URL.
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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.
+ */
+
+#include
+#include
+
+#include "ts/ts.h"
+#include "redo_cache_lookup_config.h"
+
+#define PLUGIN_NAME "redo_cache_lookup"
+
+namespace
+{
+DbgCtl dbg_ctl{PLUGIN_NAME};
+
+struct RedoCacheLookupConfig {
+ RedoCacheLookupConfig(std::string_view fallback) : fallback(fallback) {}
+
+ std::string fallback;
+};
+
+int
+handle_cache_lookup_complete(TSCont contp, TSEvent event, void *edata)
+{
+ if (event != TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE) {
+ return 0;
+ }
+
+ TSHttpTxn txnp = static_cast(edata);
+ auto *config = static_cast(TSContDataGet(contp));
+ int status = TS_CACHE_LOOKUP_MISS;
+
+ if (TSHttpTxnCacheLookupStatusGet(txnp, &status) != TS_SUCCESS || status == TS_CACHE_LOOKUP_MISS ||
+ status == TS_CACHE_LOOKUP_SKIPPED) {
+ Dbg(dbg_ctl, "rewinding to check for fallback url: %s", config->fallback.c_str());
+ TSHttpTxnRedoCacheLookup(txnp, config->fallback.c_str(), static_cast(config->fallback.size()));
+ }
+
+ TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+ return 0;
+}
+} // namespace
+
+void
+TSPluginInit(int argc, const char *argv[])
+{
+ TSPluginRegistrationInfo info;
+
+ Dbg(dbg_ctl, "Init");
+ info.plugin_name = PLUGIN_NAME;
+ info.vendor_name = "Apache Software Foundation";
+ info.support_email = "dev@trafficserver.apache.org";
+
+ if (TSPluginRegister(&info) != TS_SUCCESS) {
+ TSError("[%s] Plugin registration failed", PLUGIN_NAME);
+ return;
+ }
+
+ auto fallback = redo_cache_lookup::parse_fallback_url(argc, argv);
+
+ if (!fallback) {
+ Dbg(dbg_ctl, "Missing fallback option.");
+ TSError("[%s] Missing fallback option", PLUGIN_NAME);
+ return;
+ }
+ Dbg(dbg_ctl, "Initialized with fallback: %s", fallback->c_str());
+
+ TSCont contp = TSContCreate(handle_cache_lookup_complete, nullptr);
+ TSContDataSet(contp, new RedoCacheLookupConfig(*fallback));
+ TSHttpHookAdd(TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, contp);
+}
diff --git a/example/plugins/c-api/redo_cache_lookup/redo_cache_lookup_config.h b/example/plugins/c-api/redo_cache_lookup/redo_cache_lookup_config.h
new file mode 100644
index 00000000000..8158e4aec0f
--- /dev/null
+++ b/example/plugins/c-api/redo_cache_lookup/redo_cache_lookup_config.h
@@ -0,0 +1,78 @@
+/** @file
+
+ Configuration helpers for the redo_cache_lookup plugin.
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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.
+ */
+
+#pragma once
+
+#include
+#include
+#include
+
+namespace redo_cache_lookup
+{
+/** Parse the configured fallback URL from plugin arguments.
+ *
+ * @param[in] argc The number of plugin argument entries in @a argv.
+ * @param[in] argv The plugin arguments supplied from @c plugin.config.
+ * @return The configured fallback URL, or @c std::nullopt if no fallback URL
+ * is configured.
+ */
+inline std::optional
+parse_fallback_url(int argc, const char *argv[])
+{
+ std::optional fallback;
+
+ static const struct option longopts[] = {
+ {"fallback", required_argument, nullptr, 'f'},
+ {nullptr, 0, nullptr, 0 },
+ };
+
+#if (!defined(kfreebsd) && defined(freebsd)) || defined(darwin)
+ optreset = 1;
+#endif
+#if defined(__GLIBC__)
+ optind = 0;
+#else
+ optind = 1;
+#endif
+ opterr = 0;
+ optarg = nullptr;
+
+ int opt = 0;
+
+ while (opt >= 0) {
+ opt = getopt_long(argc, const_cast(argv), "f:", longopts, nullptr);
+ switch (opt) {
+ case 'f':
+ fallback = optarg;
+ break;
+ case -1:
+ case '?':
+ break;
+ default:
+ return std::nullopt;
+ }
+ }
+
+ return fallback;
+}
+} // namespace redo_cache_lookup
diff --git a/example/plugins/c-api/redo_cache_lookup/unit_tests/test_redo_cache_lookup_config.cc b/example/plugins/c-api/redo_cache_lookup/unit_tests/test_redo_cache_lookup_config.cc
new file mode 100644
index 00000000000..905af6dfe61
--- /dev/null
+++ b/example/plugins/c-api/redo_cache_lookup/unit_tests/test_redo_cache_lookup_config.cc
@@ -0,0 +1,66 @@
+/** @file
+
+ Tests for redo_cache_lookup plugin configuration parsing.
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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.
+ */
+
+#include
+
+#include
+
+#include "redo_cache_lookup_config.h"
+
+TEST_CASE("redo_cache_lookup fallback option is copied", "[redo_cache_lookup]")
+{
+ char plugin_name[] = "redo_cache_lookup.so";
+ char fallback_opt[] = "--fallback";
+ char fallback_url[] = "http://example.test/fallback";
+ const char *argv[] = {plugin_name, fallback_opt, fallback_url};
+
+ auto parsed = redo_cache_lookup::parse_fallback_url(3, argv);
+
+ REQUIRE(parsed.has_value());
+
+ std::memset(fallback_url, 'x', sizeof(fallback_url) - 1);
+
+ REQUIRE(*parsed == "http://example.test/fallback");
+}
+
+TEST_CASE("redo_cache_lookup fallback option accepts short form", "[redo_cache_lookup]")
+{
+ char plugin_name[] = "redo_cache_lookup.so";
+ char fallback_opt[] = "-f";
+ char fallback_url[] = "http://example.test/short";
+ const char *argv[] = {plugin_name, fallback_opt, fallback_url};
+
+ auto parsed = redo_cache_lookup::parse_fallback_url(3, argv);
+
+ REQUIRE(parsed == "http://example.test/short");
+}
+
+TEST_CASE("redo_cache_lookup fallback option is required", "[redo_cache_lookup]")
+{
+ char plugin_name[] = "redo_cache_lookup.so";
+ const char *argv[] = {plugin_name};
+
+ auto parsed = redo_cache_lookup::parse_fallback_url(1, argv);
+
+ REQUIRE_FALSE(parsed.has_value());
+}
diff --git a/include/cripts/Configs.hpp b/include/cripts/Configs.hpp
index 6826f64247f..79460327e7d 100644
--- a/include/cripts/Configs.hpp
+++ b/include/cripts/Configs.hpp
@@ -154,6 +154,8 @@ class Proxy
cripts::IntConfig connect_attempts_max_retries{"proxy.config.http.connect_attempts_max_retries"};
cripts::IntConfig connect_attempts_max_retries_down_server{"proxy.config.http.connect_attempts_max_retries_down_server"};
+ cripts::IntConfig connect_attempts_max_retries_suspect_server{
+ "proxy.config.http.connect_attempts_max_retries_suspect_server"};
cripts::IntConfig connect_attempts_rr_retries{"proxy.config.http.connect_attempts_rr_retries"};
cripts::IntConfig connect_attempts_timeout{"proxy.config.http.connect_attempts_timeout"};
cripts::IntConfig default_buffer_size{"proxy.config.http.default_buffer_size"};
@@ -357,6 +359,7 @@ class Proxy
{
public:
cripts::StringConfig filename{"proxy.config.ssl.client.CA.cert.filename"};
+ cripts::StringConfig path{"proxy.config.ssl.client.CA.cert.path"};
}; // End class Cert
public:
diff --git a/include/cripts/Connections.hpp b/include/cripts/Connections.hpp
index a8e851d7ae1..88a76e896e2 100644
--- a/include/cripts/Connections.hpp
+++ b/include/cripts/Connections.hpp
@@ -459,10 +459,9 @@ class ConnBase
void virtual _initialize() { _initialized = true; }
- cripts::Transaction *_state = nullptr;
- struct sockaddr const *_socket = nullptr;
- TSVConn _vc = nullptr;
- char _str[INET6_ADDRSTRLEN + 1];
+ cripts::Transaction *_state = nullptr;
+ struct sockaddr const *_socket = nullptr;
+ TSVConn _vc = nullptr;
bool _initialized = false;
}; // End class ConnBase
diff --git a/include/cripts/Context.hpp b/include/cripts/Context.hpp
index 00bc36eafc6..19b92bc77a5 100644
--- a/include/cripts/Context.hpp
+++ b/include/cripts/Context.hpp
@@ -18,6 +18,7 @@
#pragma once
#include
+#include
#include
#include "ts/ts.h"
#include "ts/remap.h"
@@ -28,7 +29,7 @@
#include "cripts/Connections.hpp"
// These are pretty arbitrary for now
-constexpr int CONTEXT_DATA_SLOTS = 4;
+constexpr int CONTEXT_DATA_SLOTS = 16;
namespace cripts
{
@@ -132,23 +133,16 @@ class Context
} _cache;
struct _UrlBlock {
- cripts::Client::URL &request;
- cripts::Pristine::URL pristine;
- cripts::Parent::URL parent;
+ cripts::Client::URL &request;
+ std::unique_ptr pristine;
+ std::unique_ptr parent;
struct {
- cripts::Remap::From::URL from;
- cripts::Remap::To::URL to;
+ std::unique_ptr from;
+ std::unique_ptr to;
} remap;
- _UrlBlock(Context *ctx, cripts::Client::URL &alias) : request(alias)
- {
- request.set_context(ctx);
- pristine.set_context(ctx);
- parent.set_context(ctx);
- remap.from.set_context(ctx);
- remap.to.set_context(ctx);
- }
+ _UrlBlock(Context *ctx, cripts::Client::URL &alias) : request(alias) { request.set_context(ctx); }
} _urls;
}; // End class Context
diff --git a/include/cripts/Epilogue.hpp b/include/cripts/Epilogue.hpp
index 715673ccb05..ce83f07476c 100644
--- a/include/cripts/Epilogue.hpp
+++ b/include/cripts/Epilogue.hpp
@@ -459,7 +459,7 @@ http_txn_cont(TSCont contp, TSEvent event, void *edata)
}
}
}
- if (context->state.enabled_hooks & cripts::Callbacks::DO_TXN_CLOSE) {
+ if (context->state.enabled_hooks & cripts::Callbacks::DO_SEND_REQUEST) {
CDebug("Entering do_send_request()");
wrap_send_request(context, true, CaseArg);
} else if (context->state.enabled_hooks & cripts::Callbacks::GLB_SEND_REQUEST) {
diff --git a/include/cripts/Error.hpp b/include/cripts/Error.hpp
index da674abe600..f0957c7bf68 100644
--- a/include/cripts/Error.hpp
+++ b/include/cripts/Error.hpp
@@ -17,6 +17,7 @@
*/
#pragma once
+#include
#include
#include "ts/ts.h"
@@ -132,10 +133,10 @@ class Error
void Execute(cripts::Context *context);
private:
- Reason _reason;
- Status _status;
- bool _failed = false;
- bool _redirect = false;
+ std::unique_ptr _reason;
+ Status _status;
+ bool _failed = false;
+ bool _redirect = false;
};
} // namespace cripts
diff --git a/include/cripts/Instance.hpp b/include/cripts/Instance.hpp
index bf0af5f622b..d29905a2d1f 100644
--- a/include/cripts/Instance.hpp
+++ b/include/cripts/Instance.hpp
@@ -18,9 +18,12 @@
#pragma once
#include
+#include
#include
#include
+#include
+
#include "ts/ts.h"
#include "ts/remap.h"
@@ -106,7 +109,7 @@ class Instance
debug(fmt::format_string fmt, T &&...args) const
{
if (DebugOn()) {
- auto str = fmt::vformat(fmt, fmt::make_format_args(args...));
+ auto str = fmt::format(fmt, std::forward(args)...);
Dbg(dbg_ctl_cript, "%s", str.c_str());
}
diff --git a/include/cripts/Lulu.hpp b/include/cripts/Lulu.hpp
index 4025b8527b6..7e3265c2716 100644
--- a/include/cripts/Lulu.hpp
+++ b/include/cripts/Lulu.hpp
@@ -26,7 +26,7 @@
#include
#include
-#include
+#include
#include "swoc/TextView.h"
#include "ts/ts.h"
diff --git a/include/cripts/Preamble.hpp b/include/cripts/Preamble.hpp
index 5a2b4b8e969..8d8d5f0bf6d 100644
--- a/include/cripts/Preamble.hpp
+++ b/include/cripts/Preamble.hpp
@@ -27,7 +27,7 @@
#include
#include // Useful for debugging
-#include
+#include
#include "ts/ts.h"
#include "ts/remap.h"
diff --git a/include/cripts/Urls.hpp b/include/cripts/Urls.hpp
index c28f1d1dab8..3ed143bae2f 100644
--- a/include/cripts/Urls.hpp
+++ b/include/cripts/Urls.hpp
@@ -18,6 +18,7 @@
#pragma once
#include
+#include
#include
#include
#include
@@ -334,11 +335,30 @@ class Url
using Component::Component;
+ // _state is deep-copied, but the segment `string_view`s (and Component::_owner) still
+ // reference the source URL. The copy's views dangle if the source URL's path is rewritten,
+ // and Flush()/operator= on the copy write back to the source URL via TSUrlPathSet.
+ Path(const self_type &o) : Component(o), _state(o._state ? std::make_unique(*o._state) : nullptr) {}
+
+ self_type &
+ operator=(const self_type &o)
+ {
+ if (this != &o) {
+ auto new_state = o._state ? std::make_unique(*o._state) : nullptr;
+ Component::operator=(o);
+ _state = std::move(new_state);
+ }
+ return *this;
+ }
+
+ Path(self_type &&) = default;
+ self_type &operator=(self_type &&) = default;
+
void Reset() override;
cripts::string_view GetSV() override;
cripts::string operator+=(cripts::string_view add);
- self_type operator=(cripts::string_view path);
+ self_type &operator=(cripts::string_view path);
String operator[](Segments::size_type ix);
void
@@ -346,8 +366,10 @@ class Url
{
auto p = operator[](ix);
- _size -= p.size();
- p.operator=("");
+ if (_state && ix < _state->segments.size()) {
+ _state->size -= p.size();
+ p.operator=("");
+ }
}
void
@@ -368,7 +390,7 @@ class Url
void
Flush()
{
- if (_modified) {
+ if (_state && _state->modified) {
operator=(GetSV());
}
}
@@ -376,10 +398,23 @@ class Url
private:
void _parser();
- bool _modified = false;
- Segments _segments; // Lazy loading on this
- cripts::string _storage; // Used when recombining the segments into a full path
- cripts::string::size_type _size = 0; // Mostly a guestimate for managing _storage
+ struct State {
+ bool modified = false;
+ Segments segments; // Ordered list of path segments
+ cripts::string storage; // Used when recombining the segments into a full path
+ cripts::string::size_type size = 0; // Mostly a guestimate for managing storage
+ };
+
+ State &
+ _ensure_state()
+ {
+ if (!_state) {
+ _state = std::make_unique();
+ }
+ return *_state;
+ }
+
+ std::unique_ptr _state; // Lazily allocated when path is parsed or modified
}; // End class Url::Path
@@ -461,18 +496,37 @@ class Url
using Component::Component;
- Query(cripts::string_view load)
+ Query(cripts::string_view load) : _state(std::make_unique())
{
- _data = load;
- _size = load.size();
- _loaded = true;
- _standalone = true;
+ _data = load;
+ _state->size = load.size();
+ _loaded = true;
+ _state->standalone = true;
}
+ // _state is deep-copied, but the parameter `string_view`s (and Component::_owner) still
+ // reference the source URL. The copy's views dangle if the source URL's query is rewritten,
+ // and Flush()/operator= on the copy write back to the source URL via TSUrlHttpQuerySet.
+ Query(const self_type &o) : Component(o), _state(o._state ? std::make_unique(*o._state) : nullptr) {}
+
+ self_type &
+ operator=(const self_type &o)
+ {
+ if (this != &o) {
+ auto new_state = o._state ? std::make_unique(*o._state) : nullptr;
+ Component::operator=(o);
+ _state = std::move(new_state);
+ }
+ return *this;
+ }
+
+ Query(self_type &&) = default;
+ self_type &operator=(self_type &&) = default;
+
void Reset() override;
cripts::string_view GetSV() override;
- self_type operator=(cripts::string_view query);
+ self_type &operator=(cripts::string_view query);
cripts::string operator+=(cripts::string_view add);
Parameter operator[](cripts::string_view param);
void Erase(cripts::string_view param);
@@ -482,7 +536,9 @@ class Url
Erase()
{
operator=("");
- _size = 0;
+ if (_state) {
+ _state->size = 0;
+ }
}
void
@@ -503,14 +559,14 @@ class Url
// Make sure the hash and vector are populated
_parser();
- std::ranges::sort(_ordered);
- _modified = true;
+ std::ranges::sort(_state->ordered);
+ _state->modified = true;
}
void
Flush()
{
- if (_modified) {
+ if (_state && _state->modified) {
operator=(GetSV());
}
}
@@ -518,19 +574,33 @@ class Url
private:
void _parser();
- bool _modified = false;
- bool _standalone = false; // This component is used outside of a URL owner, not common
- OrderedParams _ordered; // Ordered vector of all parameters, can be sorted etc.
- HashParams _hashed; // Unordered map to go from "name" to the query parameter
- cripts::string _storage; // Used when recombining the query params into a
- // full query string
- cripts::string::size_type _size = 0; // Mostly a guesttimate
+ struct State {
+ bool modified = false;
+ bool standalone = false; // This component is used outside of a URL owner, not common
+ OrderedParams ordered; // Ordered vector of all parameters, can be sorted etc.
+ HashParams hashed; // Unordered map to go from "name" to the query parameter
+ cripts::string storage; // Used when recombining the query params into a full query string
+ cripts::string::size_type size = 0; // Mostly a guesttimate
+ };
+
+ State &
+ _ensure_state()
+ {
+ if (!_state) {
+ _state = std::make_unique();
+ }
+ return *_state;
+ }
+
+ std::unique_ptr _state; // Lazily allocated when query is parsed or modified
}; // End class Url::Query
public:
Url() : scheme(this), host(this), port(this), path(this), query(this) {}
+ virtual ~Url() = default;
+
// Clear anything "cached" in the Url, this is rather draconian, but it's safe...
virtual void
Reset()
diff --git a/include/iocore/aio/AIO.h b/include/iocore/aio/AIO.h
index e2b3bbf301a..332e2ca43c4 100644
--- a/include/iocore/aio/AIO.h
+++ b/include/iocore/aio/AIO.h
@@ -79,10 +79,10 @@ struct AIOCallback : public Continuation {
EThread *thread = AIO_CALLBACK_THREAD_ANY;
AIOCallback *then = nullptr;
// set on return from aio_read/aio_write
- int64_t aio_result = 0;
- AIO_Reqs *aio_req = nullptr;
- ink_hrtime sleep_time = 0;
- bool from_api = false;
+ int64_t aio_result = 0;
+ AIO_Reqs *aio_req = nullptr;
+ ink_hrtime sleep_time = 0;
+ bool from_ts_api = false;
SLINK(AIOCallback, alink); /* for AIO_Reqs::aio_temp_list */
#if TS_USE_LINUX_IO_URING
iovec iov = {}; // this is to support older kernels that only support readv/writev
diff --git a/include/iocore/cache/Cache.h b/include/iocore/cache/Cache.h
index 3a7da523b56..9a0fe5d430b 100644
--- a/include/iocore/cache/Cache.h
+++ b/include/iocore/cache/Cache.h
@@ -36,8 +36,9 @@ static constexpr ts::ModuleVersion CACHE_MODULE_VERSION(1, 0);
#define SCAN_KB_PER_SECOND 8192 // 1TB/8MB = 131072 = 36 HOURS to scan a TB
-#define RAM_CACHE_ALGORITHM_CLFUS 0
-#define RAM_CACHE_ALGORITHM_LRU 1
+#define RAM_CACHE_ALGORITHM_CLFUS 0
+#define RAM_CACHE_ALGORITHM_LRU 1
+#define RAM_CACHE_ALGORITHM_S3FIFO 2
#define CACHE_COMPRESSION_NONE 0
#define CACHE_COMPRESSION_FASTLZ 1
diff --git a/include/iocore/eventsystem/EThread.h b/include/iocore/eventsystem/EThread.h
index 99569b9fffc..ff3b0e0933f 100644
--- a/include/iocore/eventsystem/EThread.h
+++ b/include/iocore/eventsystem/EThread.h
@@ -47,7 +47,6 @@ using hwloc_obj_t = hwloc_obj *;
// instead.
#define MUTEX_RETRY_DELAY HRTIME_MSECONDS(20)
-class DiskHandler;
struct EventIO;
class ServerSessionPool;
@@ -328,12 +327,6 @@ class EThread : public Thread
/** Block of memory to allocate thread specific data e.g. stat system arrays. */
char thread_private[PER_THREAD_DATA];
- /** Private Data for the Disk Processor. */
- DiskHandler *diskHandler = nullptr;
-
- /** Private Data for AIO. */
- Que(Continuation, link) aio_ops;
-
ProtectedQueue EventQueueExternal;
PriorityEventQueue EventQueue;
diff --git a/include/iocore/eventsystem/Thread.h b/include/iocore/eventsystem/Thread.h
index 0a34dd633cf..435762e71e8 100644
--- a/include/iocore/eventsystem/Thread.h
+++ b/include/iocore/eventsystem/Thread.h
@@ -134,6 +134,7 @@ class Thread
ProxyAllocator openDirEntryAllocator;
ProxyAllocator ramCacheCLFUSEntryAllocator;
ProxyAllocator ramCacheLRUEntryAllocator;
+ ProxyAllocator ramCacheS3FIFOEntryAllocator;
ProxyAllocator evacuationBlockAllocator;
ProxyAllocator ioDataAllocator;
ProxyAllocator ioAllocator;
diff --git a/include/iocore/hostdb/HostDBProcessor.h b/include/iocore/hostdb/HostDBProcessor.h
index fecb1abef7b..1a274a10e02 100644
--- a/include/iocore/hostdb/HostDBProcessor.h
+++ b/include/iocore/hostdb/HostDBProcessor.h
@@ -123,10 +123,53 @@ enum class HostDBType : uint8_t {
};
/** Information about a single target.
+ *
+ * Each instance tracks the health state of one upstream address. The state is derived from @c _last_failure and the caller-supplied
+ * @a fail_window:
+ *
+ * | State | Description |
+ * |---------|-----------------------------------------------------------------------------------|
+ * | Up | No known failure; eligible for normal selection. |
+ * | Down | Blocked; no connections permitted until @c _last_failure + @a fail_window elapses |
+ * | Suspect | Fail window has elapsed; connections are permitted. |
+ * | | On success transitions to Up (@c mark_up); on failure returns to Down. |
+ *
+ * State transition diagram:
+ *
+ * @startuml
+ * hide empty description
+ *
+ * [*] --> Up
+ * Up --> Down : connect failure\n(mark_down)
+ * Down --> Suspect : fail_window elapses
+ * Suspect --> Up : connect success\n(mark_up)
+ * Suspect --> Down : connect failure\n(mark_down)
+ * @enduml
+ *
+ * State transition and `fail_window` time chart:
+ *
+ * @code
+ * |<-- fail_window -->|
+ * -+----------+--------------------+--------------------+----------+----> time
+ * | Up | Down | Suspect | Up |
+ * -+----------+--------------------+--------------------+----------+---->
+ * ^ ^ ^
+ * \ \ \
+ * (_last_failure) (_last_failure + fail_window) (connect success)
+ * @endcode
*/
-struct HostDBInfo {
+class HostDBInfo
+{
+public:
using self_type = HostDBInfo; ///< Self reference type.
+ /// Health state of this target.
+ enum class State {
+ UP,
+ DOWN,
+ SUSPECT,
+ };
+
/// Default constructor.
HostDBInfo() = default;
@@ -134,50 +177,23 @@ struct HostDBInfo {
/// Absolute time of when this target failed.
/// A value of zero (@c TS_TIME_ZERO ) indicates no failure.
- ts_time last_fail_time() const;
-
- /// Target is alive - no known failure.
- bool is_alive();
-
- /// Target has failed and is still in the blocked time window.
- bool is_down(ts_time now, ts_seconds fail_window);
-
- /** Select this target.
- *
- * @param now Current time.
- * @param fail_window Failure window.
- * @return Status of the selection.
- *
- * If a zombie is selected the failure time is updated to make it appear down to other threads in a thread safe
- * manner. The caller should check @c last_fail_time to see if a zombie was selected.
- */
- bool select(ts_time now, ts_seconds fail_window) const;
+ ts_time last_fail_time() const;
+ uint8_t fail_count() const;
+ char const *srvname() const;
- /** Mark the entry as down.
- *
- * @param now Time of the failure.
- * @return @c true if @a this was marked down, @c false if not.
- *
- * This can return @c false if the entry is already marked down, in which case the failure time is not updated.
- */
- bool mark_down(ts_time now);
+ /// Return the current health state of this target.
+ State state(ts_time now, ts_seconds fail_window) const;
- std::pair increment_fail_count(ts_time now, uint8_t max_retries);
+ // Sugars of checking state
+ bool is_up() const;
+ bool is_down(ts_time now, ts_seconds fail_window) const;
+ bool is_suspect(ts_time now, ts_seconds fail_window) const;
- /** Mark the target as up / alive.
- *
- * @return Previous alive state of the target.
- */
- bool mark_up();
-
- char const *srvname() const;
+ // State controllers
+ bool mark_up();
+ bool mark_down(ts_time now, ts_seconds fail_window);
+ std::pair increment_fail_count(ts_time now, uint8_t max_retries, ts_seconds fail_window);
- /** Migrate data after a DNS update.
- *
- * @param that Source item.
- *
- * This moves only specific state information, it is not a generic copy.
- */
void migrate_from(self_type const &that);
/// A target is either an IP address or an SRV record.
@@ -187,16 +203,8 @@ struct HostDBInfo {
SRVInfo srv; ///< SRV record.
} data{IpAddr{}};
- /// Data that migrates after updated DNS records are processed.
- /// @see migrate_from
- /// @{
- /// Last time a failure was recorded.
- std::atomic last_failure{TS_TIME_ZERO};
- /// Count of connection failures
- std::atomic fail_count{0};
/// Expected HTTP version of the target based on earlier transactions.
HTTPVersion http_version = HTTP_INVALID;
- /// @}
self_type &assign(IpAddr const &addr);
@@ -207,96 +215,11 @@ struct HostDBInfo {
HostDBType type = HostDBType::UNSPEC; ///< Invalid data.
friend HostDBContinuation;
-};
-inline HostDBInfo &
-HostDBInfo::operator=(HostDBInfo const &that)
-{
- if (this != &that) {
- memcpy(static_cast(this), static_cast(&that), sizeof(*this));
- }
- return *this;
-}
-
-inline ts_time
-HostDBInfo::last_fail_time() const
-{
- return last_failure;
-}
-
-inline bool
-HostDBInfo::is_alive()
-{
- return this->last_fail_time() == TS_TIME_ZERO;
-}
-
-/**
- Check if this HostDBInfo is currently marked DOWN (true) or UP (false). Returns true while within the `fail_window` period after
- `last_failure`. Once `fail_window` expires, the host is treated as UP and this function returns false.
-
- |<-- fail_window -->|
- ----------------+-------------------+-----------------> time
- UP | DOWN | UP
- (is_down=false) | (is_down=true) | (is_down=false)
- | |
- ^ ^
- \ \
- last_failure last_failure + fail_window
- */
-inline bool
-HostDBInfo::is_down(ts_time now, ts_seconds fail_window)
-{
- auto last_fail = this->last_fail_time();
- return (last_fail != TS_TIME_ZERO) && (now <= last_fail + fail_window);
-}
-
-inline bool
-HostDBInfo::mark_up()
-{
- auto t = last_failure.exchange(TS_TIME_ZERO);
- bool was_down = t != TS_TIME_ZERO;
- if (was_down) {
- fail_count.store(0);
- }
- return was_down;
-}
-
-inline bool
-HostDBInfo::mark_down(ts_time now)
-{
- auto t0{TS_TIME_ZERO};
- return last_failure.compare_exchange_strong(t0, now);
-}
-
-inline std::pair
-HostDBInfo::increment_fail_count(ts_time now, uint8_t max_retries)
-{
- auto fcount = ++fail_count;
- bool marked_down = false;
- if (fcount >= max_retries) {
- marked_down = mark_down(now);
- }
- return std::make_pair(marked_down, fcount);
-}
-
-inline bool
-HostDBInfo::select(ts_time now, ts_seconds fail_window) const
-{
- auto t0 = this->last_fail_time();
- if (t0 == TS_TIME_ZERO) {
- return true; // it's alive and so is valid for selection.
- }
- // Return true and give it a try if enough time is elapsed since the last failure
- return (t0 + fail_window < now);
-}
-
-inline void
-HostDBInfo::migrate_from(HostDBInfo::self_type const &that)
-{
- this->last_failure = that.last_failure.load();
- this->fail_count = that.fail_count.load();
- this->http_version = that.http_version;
-}
+private:
+ std::atomic _last_failure{TS_TIME_ZERO}; ///< Last time a failure was recorded
+ std::atomic _fail_count{0}; ///< Count of connection failures
+};
// ----
/** Root item for HostDB.
@@ -371,15 +294,12 @@ class HostDBRecord : public RefCountObj
/** Pick the next round robin and update the record atomically.
*
- * @note This may select a zombie server and reserve it for the caller, therefore the caller must
- * attempt to connect to the selected target if possible.
- *
- * @param now Current time to use for aliveness calculations.
- * @param fail_window Blackout time for down servers.
- * @return Status of the updated target.
+ * @note This may select a suspect server. The caller must attempt to connect to the selected
+ * target if possible.
*
- * If the return value is @c HostDBInfo::Status::DOWN this means all targets are down and there is
- * no valid upstream.
+ * @param[in] now Current time to use for HostDBInfo state calculations.
+ * @param[in] fail_window Blackout time for down servers.
+ * @return The selected target, or @c nullptr if all targets are down.
*
* @note Concurrency - this is not done under lock and depends on the caller for correct use.
* For strict round robin, it is a feature that every call will get a distinct index. For
@@ -434,9 +354,9 @@ class HostDBRecord : public RefCountObj
* This accounts for the round robin setting. The default is to use "client affinity" in
* which case @a hash_addr is as a hash seed to select the target.
*
- * This may select a zombie target, which can be detected by checking the target's last
- * failure time. If it is not @c TS_TIME_ZERO the target is a zombie. Other transactions will
- * be blocked from selecting that target until @a fail_window time has passed.
+ * This may select a suspect target (fail window elapsed, connections permitted again), which can
+ * be detected by checking the target's last failure time. If it is not @c TS_TIME_ZERO the target
+ * is a suspect. Multiple threads may concurrently select the same suspect target.
*
* In cases other than strict round robin, a base target is selected. If valid, that is returned,
* but if not then the targets in this record are searched until a valid one is found. The result
@@ -588,7 +508,7 @@ struct ResolveInfo {
/// Keep a reference to the base HostDB object, so it doesn't get GC'd.
Ptr record;
- HostDBInfo *active = nullptr; ///< Active host record.
+ HostDBInfo *active = nullptr; ///< Active HostDBInfo
/// Working address. The meaning / source of the value depends on other elements.
/// This is the "resolved" address if @a resolved_p is @c true.
@@ -646,22 +566,23 @@ struct ResolveInfo {
*/
bool resolve_immediate();
- /** Mark the active target as down.
+ /** Mark the active target as DOWN.
*
- * @param now Time of failure.
+ * @param[in] now Time of failure.
+ * @param[in] fail_window The fail window duration (proxy.config.http.down_server.cache_time).
* @return @c true if the server was marked as down, @c false if not.
*
*/
- bool mark_active_server_down(ts_time now);
+ bool mark_active_server_down(ts_time now, ts_seconds fail_window);
- /** Mark the active target as alive.
+ /** Mark the active target as UP.
*
* @return @c true if the target changed state.
*/
- bool mark_active_server_alive();
+ bool mark_active_server_up();
/// Select / resolve to the next RR entry for the record.
- bool select_next_rr();
+ bool select_next_rr(ts_time now, ts_seconds fail_window);
bool is_srv() const;
};
@@ -863,15 +784,15 @@ ResolveInfo::set_active(sockaddr const *s)
}
inline bool
-ResolveInfo::mark_active_server_alive()
+ResolveInfo::mark_active_server_up()
{
return active->mark_up();
}
inline bool
-ResolveInfo::mark_active_server_down(ts_time now)
+ResolveInfo::mark_active_server_down(ts_time now, ts_seconds fail_window)
{
- return active != nullptr && active->mark_down(now);
+ return active != nullptr && active->mark_down(now, fail_window);
}
inline bool
diff --git a/include/iocore/net/NetVConnection.h b/include/iocore/net/NetVConnection.h
index 4da60535390..41b2d80c094 100644
--- a/include/iocore/net/NetVConnection.h
+++ b/include/iocore/net/NetVConnection.h
@@ -505,6 +505,8 @@ class NetVConnection : public VConnection, public PluginUserArgs{}.swap(tlv);
+ version = ProxyProtocolVersion::UNDEFINED;
+ ip_family = AF_UNSPEC;
+ type = 0;
+ src_addr = {};
+ dst_addr = {};
+ }
+
int set_additional_data(std::string_view data);
void set_ipv4_addrs(in_addr_t src_addr, uint16_t src_port, in_addr_t dst_addr, uint16_t dst_port);
void set_ipv6_addrs(const in6_addr &src_addr, uint16_t src_port, const in6_addr &dst_addr, uint16_t dst_port);
diff --git a/include/iocore/net/SSLMultiCertConfigLoader.h b/include/iocore/net/SSLMultiCertConfigLoader.h
index d0f68469ce9..c12594cb0f6 100644
--- a/include/iocore/net/SSLMultiCertConfigLoader.h
+++ b/include/iocore/net/SSLMultiCertConfigLoader.h
@@ -109,6 +109,7 @@ class SSLMultiCertConfigLoader
virtual bool _set_npn_callback(SSL_CTX *ctx);
virtual bool _set_alpn_callback(SSL_CTX *ctx);
virtual bool _set_keylog_callback(SSL_CTX *ctx);
+ virtual bool _enable_cert_compression(SSL_CTX *ctx);
virtual bool _enable_ktls(SSL_CTX *ctx);
virtual bool _enable_early_data(SSL_CTX *ctx);
};
diff --git a/include/iocore/net/SSLSNIConfig.h b/include/iocore/net/SSLSNIConfig.h
index b71502b6e3c..64dd23a7c5d 100644
--- a/include/iocore/net/SSLSNIConfig.h
+++ b/include/iocore/net/SSLSNIConfig.h
@@ -43,6 +43,7 @@
#include "iocore/eventsystem/ConfigProcessor.h"
#include "iocore/net/SNIActionItem.h"
#include "iocore/net/YamlSNIConfig.h"
+#include "mgmt/config/ConfigContext.h"
#include
@@ -90,8 +91,8 @@ class SNIConfigParams : public ConfigInfo
~SNIConfigParams() override;
const NextHopProperty *get_property_config(const std::string &servername) const;
- bool initialize();
- bool initialize(const std::string &sni_filename);
+ bool initialize(ConfigContext ctx = {});
+ bool initialize(const std::string &sni_filename, ConfigContext ctx = {});
/** Walk sni.yaml config and populate sni_action_list
@return 0 for success, 1 is failure
*/
diff --git a/include/iocore/net/TLSBasicSupport.h b/include/iocore/net/TLSBasicSupport.h
index 015633607b1..7d9354fa558 100644
--- a/include/iocore/net/TLSBasicSupport.h
+++ b/include/iocore/net/TLSBasicSupport.h
@@ -51,6 +51,8 @@ class TLSBasicSupport
std::string_view get_tls_group() const;
ink_hrtime get_tls_handshake_begin_time() const;
ink_hrtime get_tls_handshake_end_time() const;
+ bool get_tls_handshake_bytes(uint64_t &bytes_in, uint64_t &bytes_out) const;
+
/**
* Returns a certificate that need to be verified.
*
@@ -101,6 +103,9 @@ class TLSBasicSupport
X509_STORE_CTX *_cert_to_verify = nullptr;
- ink_hrtime _tls_handshake_begin_time = 0;
- ink_hrtime _tls_handshake_end_time = 0;
+ ink_hrtime _tls_handshake_begin_time = 0;
+ ink_hrtime _tls_handshake_end_time = 0;
+ mutable bool _tls_handshake_bytes_measured = false;
+ mutable uint64_t _tls_handshake_bytes_in = 0;
+ mutable uint64_t _tls_handshake_bytes_out = 0;
};
diff --git a/include/iocore/net/quic/QUICConfig.h b/include/iocore/net/quic/QUICConfig.h
index 3bc871091aa..1ebec4f6aa0 100644
--- a/include/iocore/net/quic/QUICConfig.h
+++ b/include/iocore/net/quic/QUICConfig.h
@@ -28,6 +28,7 @@
#include "iocore/eventsystem/ConfigProcessor.h"
#include "iocore/net/SSLTypes.h"
+#include "mgmt/config/ConfigContext.h"
class QUICConfigParams : public ConfigInfo
{
@@ -35,7 +36,7 @@ class QUICConfigParams : public ConfigInfo
QUICConfigParams(){};
~QUICConfigParams();
- void initialize();
+ void initialize(ConfigContext ctx = {});
uint32_t instance_id() const;
uint32_t stateless_retry() const;
diff --git a/include/mgmt/config/ConfigContext.h b/include/mgmt/config/ConfigContext.h
index 0dbce853a1e..e788598872f 100644
--- a/include/mgmt/config/ConfigContext.h
+++ b/include/mgmt/config/ConfigContext.h
@@ -33,6 +33,7 @@
#include "swoc/Errata.h"
#include "swoc/BufferWriter.h"
+#include "tsutil/ts_diag_levels.h"
#include "yaml-cpp/node/node.h"
// Forward declarations
@@ -118,6 +119,8 @@ class ConfigContext
}
void log(std::string_view text);
+ void log(DiagsLevel level, std::string_view text);
+ void log(swoc::Errata const &errata);
template
void
log(swoc::TextView fmt, Args &&...args)
@@ -167,22 +170,38 @@ class ConfigContext
/// Each dependent reports its own status (in_progress/complete/fail) and the parent
/// task aggregates them. The dependent context also inherits the parent's supplied YAML node.
///
- [[nodiscard]] ConfigContext add_dependent_ctx(std::string_view description = "");
+ [[nodiscard]] ConfigContext add_dependent_ctx(std::string_view description = "", std::string_view filename = "");
/// Get supplied YAML node (for RPC-based reloads).
- /// A default-constructed YAML::Node is Undefined (operator bool() == false).
+ /// Returns Undefined when no content was provided (operator bool() == false).
/// @code
/// if (auto yaml = ctx.supplied_yaml()) { /* use yaml node */ }
/// @endcode
/// @return copy of the supplied YAML node (cheap — YAML::Node is internally reference-counted).
[[nodiscard]] YAML::Node supplied_yaml() const;
+ /// Get reload directives extracted from the _reload key.
+ /// Directives are operational parameters that modify how the handler performs
+ /// the reload (e.g. scope to a single entry, dry-run) — distinct from config content.
+ /// The framework extracts _reload from the supplied node before passing content
+ /// to the handler, so supplied_yaml() never contains _reload.
+ /// Returns Undefined when no directives were provided (operator bool() == false).
+ /// @code
+ /// if (auto directives = ctx.reload_directives()) { /* use directives */ }
+ /// @endcode
+ /// @return copy of the directives YAML node (cheap — YAML::Node is internally reference-counted).
+ [[nodiscard]] YAML::Node reload_directives() const;
+
private:
/// Set supplied YAML node. Only ConfigRegistry should call this during reload setup.
void set_supplied_yaml(YAML::Node node);
+ /// Set reload directives. Only ConfigRegistry should call this during reload setup.
+ void set_reload_directives(YAML::Node node);
+
std::weak_ptr _task;
- YAML::Node _supplied_yaml; ///< for no content, this will just be empty
+ YAML::Node _supplied_yaml{YAML::NodeType::Undefined};
+ YAML::Node _reload_directives{YAML::NodeType::Undefined};
friend class ReloadCoordinator;
friend class config::ConfigRegistry;
diff --git a/include/mgmt/config/ConfigContextDiags.h b/include/mgmt/config/ConfigContextDiags.h
new file mode 100644
index 00000000000..96c09c5df5a
--- /dev/null
+++ b/include/mgmt/config/ConfigContextDiags.h
@@ -0,0 +1,185 @@
+/** @file
+
+ ConfigContextDiags.h — Convenience macros for config handler logging.
+
+ These macros combine diags output (Note/Warning/Error) with ConfigContext
+ task tracking in a single call. They format the message once and send it
+ to both destinations:
+ 1. The ATS diagnostics system (diags.log / error.log)
+ 2. The ConfigContext reload task log (visible via traffic_ctl config status)
+
+ This ensures operators see the same information whether they look at
+ diags.log or query reload status via traffic_ctl / JSONRPC.
+
+ @section when When to use these macros
+
+ Use a macro when you want the message in BOTH diags and the reload task log.
+ This is the common case for operational messages in config handlers:
+
+ @code
+ CfgLoadInProgress(ctx, "%s loading ...", filename); // subtasks
+ CfgLoadLog(ctx, DL_Note, "%s loading ...", filename); // top-level handlers
+ CfgLoadComplete(ctx, "%s finished loading", filename);
+ CfgLoadFail(ctx, "%s failed to load", filename);
+ @endcode
+
+ Use ctx methods directly when you only want the reload task log (no diags):
+
+ @code
+ ctx.log("parsed %d rules", count); // task log only
+ ctx.in_progress(); // state change only, no message
+ ctx.complete(); // state change only, no message
+ @endcode
+
+ Use Dbg() directly when you only want debug output (no task log):
+
+ @code
+ Dbg(dbg_ctl_ssl, "internal detail ..."); // diags only, not in reload status
+ @endcode
+
+ Use CfgLoadDbg when you want BOTH debug output and the task log:
+
+ @code
+ CfgLoadDbg(ctx, dbg_ctl_ssl, "Reload SNI file");
+ @endcode
+
+ @section summary Quick reference
+
+ | Want diags? | Want task log? | Use |
+ |-------------|----------------|-----------------------------------|
+ | Note | yes + in_progress| CfgLoadInProgress(ctx, ...) |
+ | Note | yes + complete | CfgLoadComplete(ctx, ...) |
+ | Error | yes + fail | CfgLoadFail(ctx, ...) |
+ | Err + Errata| yes + fail | CfgLoadFailWithErrata(...) |
+ | Note/Warn | yes (no state) | CfgLoadLog(ctx, DL_xxx, ...) |
+ | Dbg(tag) | yes | CfgLoadDbg(ctx, ctl, ...) |
+ | no | yes | ctx.log(...) |
+ | no | yes + state | ctx.complete() / ctx.fail() |
+ | yes | no | Note/Warning/Error/Dbg directly |
+
+ @section errata Errata handling
+
+ For failures with swoc::Errata detail, use CfgLoadFailWithErrata to
+ combine the diags summary, errata detail, and state change in one call:
+
+ @code
+ CfgLoadFailWithErrata(ctx, errata, "%s failed to load", filename);
+ @endcode
+
+ This logs the formatted message to diags and the task log at the given
+ severity, appends each errata annotation (with its own severity) to the
+ task log, and marks the task as FAIL.
+
+ @section fatal Fatal errors
+
+ Fatal/Emergency terminate the process — reload status is irrelevant.
+ Call Fatal() directly; do not use these macros for it.
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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.
+*/
+
+#pragma once
+
+#include "mgmt/config/ConfigContext.h"
+#include "tscore/Diags.h"
+
+/// Log a Note and mark the context as IN_PROGRESS.
+/// The framework sets IN_PROGRESS on handler tasks automatically, so
+/// CfgLoadLog(ctx, DL_Note, ...) is preferred for top-level "loading..."
+/// messages. Use this macro for subtasks created via add_dependent_ctx().
+///
+/// CfgLoadInProgress(ctx, "%s loading ...", ts::filename::IP_ALLOW);
+///
+#define CfgLoadInProgress(CTX, FMT, ...) \
+ do { \
+ char _cfgctx_buf[1024]; \
+ snprintf(_cfgctx_buf, sizeof(_cfgctx_buf), FMT, ##__VA_ARGS__); \
+ Note("%s", _cfgctx_buf); \
+ (CTX).in_progress(_cfgctx_buf); \
+ } while (false)
+
+/// Log a Note and mark the context as SUCCESS.
+/// Use when a config load/reload operation finishes successfully.
+///
+/// CfgLoadComplete(ctx, "%s finished loading", ts::filename::IP_ALLOW);
+///
+#define CfgLoadComplete(CTX, FMT, ...) \
+ do { \
+ char _cfgctx_buf[1024]; \
+ snprintf(_cfgctx_buf, sizeof(_cfgctx_buf), FMT, ##__VA_ARGS__); \
+ Note("%s", _cfgctx_buf); \
+ (CTX).complete(_cfgctx_buf); \
+ } while (false)
+
+/// Log an Error and mark the context as FAIL.
+/// Use when a config load/reload operation fails. Fail always implies
+/// DL_Error — if the condition is merely degraded (not fatal to the load),
+/// use CfgLoadLog(ctx, DL_Warning, ...) + CfgLoadComplete() instead.
+///
+/// CfgLoadFail(ctx, "%s failed to load", ts::filename::IP_ALLOW);
+///
+#define CfgLoadFail(CTX, FMT, ...) \
+ do { \
+ char _cfgctx_buf[1024]; \
+ snprintf(_cfgctx_buf, sizeof(_cfgctx_buf), FMT, ##__VA_ARGS__); \
+ DiagsError(DL_Error, "%s", _cfgctx_buf); \
+ (CTX).log(DL_Error, _cfgctx_buf); \
+ (CTX).fail(); \
+ } while (false)
+
+/// Log an Error, append errata detail to the task log, and mark the context
+/// as FAIL. Combines CfgLoadFail + ctx.fail(errata) in one call.
+///
+/// CfgLoadFailWithErrata(ctx, errata, "%s failed to load", filename);
+///
+#define CfgLoadFailWithErrata(CTX, ERRATA, FMT, ...) \
+ do { \
+ char _cfgctx_buf[1024]; \
+ snprintf(_cfgctx_buf, sizeof(_cfgctx_buf), FMT, ##__VA_ARGS__); \
+ DiagsError(DL_Error, "%s", _cfgctx_buf); \
+ (CTX).log(DL_Error, _cfgctx_buf); \
+ (CTX).fail(ERRATA); \
+ } while (false)
+
+/// Log at the given DiagsLevel and add to the task log, without changing state.
+/// Use for intermediate informational messages during load/reload.
+///
+/// CfgLoadLog(ctx, DL_Note, "loaded %d categories from %s", count, filename);
+///
+#define CfgLoadLog(CTX, LEVEL, FMT, ...) \
+ do { \
+ char _cfgctx_buf[1024]; \
+ snprintf(_cfgctx_buf, sizeof(_cfgctx_buf), FMT, ##__VA_ARGS__); \
+ DiagsError(LEVEL, "%s", _cfgctx_buf); \
+ (CTX).log(LEVEL, _cfgctx_buf); \
+ } while (false)
+
+/// Log via a DbgCtl (debug-level, conditional on the tag) and add to the task
+/// log. The debug output only appears when the tag is enabled; the task log
+/// always receives the message.
+///
+/// CfgLoadDbg(ctx, dbg_ctl_ssl, "Reload SNI file");
+///
+#define CfgLoadDbg(CTX, CTL, FMT, ...) \
+ do { \
+ char _cfgctx_buf[1024]; \
+ snprintf(_cfgctx_buf, sizeof(_cfgctx_buf), FMT, ##__VA_ARGS__); \
+ Dbg((CTL), "%s", _cfgctx_buf); \
+ (CTX).log(DL_Debug, _cfgctx_buf); \
+ } while (false)
diff --git a/include/mgmt/config/ConfigReloadTrace.h b/include/mgmt/config/ConfigReloadTrace.h
index e52078e0b9e..1db523e8a7a 100644
--- a/include/mgmt/config/ConfigReloadTrace.h
+++ b/include/mgmt/config/ConfigReloadTrace.h
@@ -171,6 +171,16 @@ class ConfigReloadTask : public std::enable_shared_from_this
return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count();
}
+ /// A single log entry with optional severity.
+ /// Entries from log(level, text) carry the supplied DiagsLevel. State-change
+ /// convenience methods attach an implicit level: in_progress() and complete()
+ /// log at DL_Note, fail() logs at DL_Error. The one-argument log(text) form
+ /// stores DL_Undefined and is always displayed (never filtered by --min-level).
+ struct LogEntry {
+ DiagsLevel level{DL_Undefined}; ///< DL_Undefined = always shown (filter bypass)
+ std::string text;
+ };
+
struct Info {
friend class ConfigReloadTask;
/// Grant friendship to the specific YAML::convert specialization.
@@ -184,7 +194,7 @@ class ConfigReloadTask : public std::enable_shared_from_this
protected:
int64_t created_time_ms{now_ms()}; ///< milliseconds since epoch
int64_t last_updated_time_ms{now_ms()}; ///< last time this task was updated (ms)
- std::vector logs; ///< log messages from handler
+ std::vector logs; ///< log messages from handler
State state{State::CREATED};
std::string token;
std::string description;
@@ -211,9 +221,10 @@ class ConfigReloadTask : public std::enable_shared_from_this
/// Create a child sub-task and return a ConfigContext wrapping it.
/// The child inherits the parent's token and if passed, the supplied YAML content.
- [[nodiscard]] ConfigContext add_child(std::string_view description = "");
+ [[nodiscard]] ConfigContext add_child(std::string_view description = "", std::string_view filename = "");
self_type &log(std::string const &text);
+ self_type &log(DiagsLevel level, std::string const &text);
void set_completed();
void set_failed();
void set_in_progress();
@@ -297,7 +308,7 @@ class ConfigReloadTask : public std::enable_shared_from_this
/// Mark task as TIMEOUT with an optional reason logged
void mark_as_bad_state(std::string_view reason = "");
- [[nodiscard]] std::vector
+ [[nodiscard]] std::vector
get_logs() const
{
std::shared_lock lock(_mutex);
@@ -356,8 +367,13 @@ class ConfigReloadTask : public std::enable_shared_from_this
void notify_parent();
void set_state_and_notify(State state);
+ friend struct ConfigReloadProgress;
+ void log_reload_summary(State final_state);
+ static void dump_subtask_tree(const std::vector &tasks, int indent);
+
mutable std::shared_mutex _mutex;
bool _reload_progress_checker_started{false};
+ bool _summary_logged{false};
Info _info;
ConfigReloadTaskPtr _parent; ///< parent task, if any
diff --git a/include/proxy/ControlMatcher.h b/include/proxy/ControlMatcher.h
index 96e9c36e966..505f09e343d 100644
--- a/include/proxy/ControlMatcher.h
+++ b/include/proxy/ControlMatcher.h
@@ -99,6 +99,8 @@
#include
+#include "mgmt/config/ConfigContext.h"
+
#ifdef HAVE_CTYPE_H
#include
#endif
@@ -302,11 +304,12 @@ template class ControlMatcher
public:
// Parameter name must not be deallocated before this object is
ControlMatcher(const char *file_var, const char *name, const matcher_tags *tags,
- int flags_in = (ALLOW_HOST_TABLE | ALLOW_IP_TABLE | ALLOW_REGEX_TABLE | ALLOW_HOST_REGEX_TABLE | ALLOW_URL_TABLE));
+ int flags_in = (ALLOW_HOST_TABLE | ALLOW_IP_TABLE | ALLOW_REGEX_TABLE | ALLOW_HOST_REGEX_TABLE | ALLOW_URL_TABLE),
+ ConfigContext ctx = {});
~ControlMatcher();
- int BuildTable();
- int BuildTableFromString(char *str);
+ int BuildTable(ConfigContext ctx = {});
+ int BuildTableFromString(char *str, ConfigContext ctx = {});
void Match(RequestData *rdata, MatchResult *result) const;
void Print() const;
diff --git a/include/proxy/HostStatus.h b/include/proxy/HostStatus.h
index fdf2de2d3bb..8fd9e1358aa 100644
--- a/include/proxy/HostStatus.h
+++ b/include/proxy/HostStatus.h
@@ -34,7 +34,7 @@
#include "records/RecCore.h"
#include "tscore/Filenames.h"
#include "tscore/Layout.h"
-#include "tscore/ink_rwlock.h"
+#include "tsutil/Bravo.h"
#include
#include
@@ -252,5 +252,5 @@ struct HostStatus {
// next hop status, key is hostname or ip string, data is HostStatRec
std::unordered_map hosts_statuses;
- ink_rwlock host_status_rwlock;
+ ts::bravo::shared_mutex host_status_rwlock;
};
diff --git a/include/proxy/NonHttpSmLogData.h b/include/proxy/NonHttpSmLogData.h
new file mode 100644
index 00000000000..552e09622ea
--- /dev/null
+++ b/include/proxy/NonHttpSmLogData.h
@@ -0,0 +1,90 @@
+/** @file
+
+ NonHttpSmLogData populates LogData for access-log entries that cannot be
+ backed by an HttpSM.
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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.
+ */
+
+#pragma once
+
+#include "proxy/Milestones.h"
+#include "proxy/hdrs/HTTP.h"
+#include "tscore/ink_inet.h"
+
+#include
+
+/** Owns access-log data for entries that cannot be backed by an @c HttpSM.
+ *
+ * Normal transaction access logging is expected to use data extracted from a
+ * live @c HttpSM. This type is for exceptional client-facing failures that need
+ * transaction-log visibility but occur before an @c HttpSM exists, such as
+ * malformed HTTP/2 or HTTP/3 request headers rejected during protocol
+ * validation. It may also be used for connection-level failures, such as TLS
+ * handshake errors, when operators need those events in the access log.
+ *
+ * Because the protocol stream or connection state may be destroyed immediately
+ * after the failure is handled, this object owns the copied headers,
+ * addresses, milestones, protocol strings, and outcome fields needed by
+ * @c LogAccess. Fields that require an @c HttpSM, origin transaction, cache
+ * lookup, or server response are intentionally left unset and marshal through
+ * the normal default values.
+ *
+ * This path should remain narrow. If an @c HttpSM exists, prefer the standard
+ * @c HttpSM-backed logging path so normal transactions do not pay for extra
+ * copying or exceptional state.
+ */
+class NonHttpSmLogData
+{
+public:
+ NonHttpSmLogData() = default;
+
+ ~NonHttpSmLogData()
+ {
+ if (owned_client_request.valid()) {
+ owned_client_request.destroy();
+ }
+ }
+
+ // ===== Owned backing storage (public for ProxyTransaction to populate). =====
+
+ HTTPHdr owned_client_request;
+ TransactionMilestones owned_milestones;
+ IpEndpoint owned_client_addr = {};
+ IpEndpoint owned_client_src_addr = {};
+ IpEndpoint owned_client_dst_addr = {};
+
+ std::string owned_method;
+ std::string owned_scheme;
+ std::string owned_authority;
+ std::string owned_path;
+ std::string owned_url;
+ std::string owned_client_protocol_str;
+
+ // ===== Simple fields (public for ProxyTransaction to set). =====
+
+ uint16_t m_client_port = 0;
+ SquidLogCode m_log_code = SquidLogCode::EMPTY;
+ SquidHitMissCode m_hit_miss_code = SQUID_MISS_NONE;
+ SquidHierarchyCode m_hier_code = SquidHierarchyCode::NONE;
+ int64_t m_connection_id = 0;
+ int m_transaction_id = 0;
+ bool m_client_connection_is_ssl = false;
+ int64_t m_server_transact_count = 0;
+};
diff --git a/include/proxy/PluginThreadContext.h b/include/proxy/PluginThreadContext.h
new file mode 100644
index 00000000000..88db0d825dd
--- /dev/null
+++ b/include/proxy/PluginThreadContext.h
@@ -0,0 +1,61 @@
+/** @file
+
+ Per-plugin identity carried on the continuations a plugin creates.
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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.
+ */
+
+#pragma once
+
+#include
+#include
+
+#include "tscore/Ptr.h"
+#include "tsutil/Metrics.h"
+
+/** Carries a plugin's identity on the continuations it creates so that
+ * proxy.process.plugin..* workload counters can be attributed back to the originating
+ * plugin DSO.
+ *
+ * This lives in ts::proxy rather than ts::http_remap because it is shared by both remap plugins
+ * (PluginDso) and global plugins (GlobalPluginContext, in Plugin.cc). The library dependency only
+ * runs ts::http_remap -> ts::proxy, so putting it here lets both paths resolve these symbols. */
+class PluginThreadContext : public RefCountObjInHeap
+{
+public:
+ virtual void acquire() = 0;
+ virtual void release() = 0;
+
+ /** Register this plugin's proxy.process.plugin..* metrics. @a plugin_name is the DSO path;
+ * only its basename stem (extension removed) is used as . */
+ void registerPluginMetrics(std::string_view plugin_name);
+
+ void countInvocation();
+
+ ts::Metrics::Counter::AtomicType *_invocations = nullptr;
+ ts::Metrics::Counter::AtomicType *_bytes = nullptr;
+ ts::Metrics::Counter::AtomicType *_transfers = nullptr;
+
+ static constexpr const char *const _tag = "plugin_context"; /** @brief log tag used by this class */
+
+private:
+ /** Derive a metric-safe token from a plugin path: the basename with the extension removed, then any
+ * character outside [A-Za-z0-9_-] replaced by '_' (e.g. "/.../header_rewrite.so" -> "header_rewrite"). */
+ static std::string _metric_token(std::string_view name);
+};
diff --git a/include/proxy/PluginVC.h b/include/proxy/PluginVC.h
index bfa8b1aa5b8..ba016754e14 100644
--- a/include/proxy/PluginVC.h
+++ b/include/proxy/PluginVC.h
@@ -38,6 +38,7 @@
#include "proxy/Plugin.h"
#include "iocore/net/NetVConnection.h"
#include "tscore/ink_atomic.h"
+#include "tsutil/Metrics.h"
class PluginVCCore;
@@ -253,6 +254,11 @@ class PluginVCCore : public Continuation
Continuation *connect_to = nullptr;
bool connected = false;
+ // Transport counters of the plugin that created this intercept, captured at alloc(). Registry-owned
+ // (process-lifetime), so safe to hold raw. Null for core-internal PluginVCs.
+ ts::Metrics::Counter::AtomicType *_bytes = nullptr;
+ ts::Metrics::Counter::AtomicType *_transfers = nullptr;
+
IpEndpoint passive_addr_struct;
IpEndpoint active_addr_struct;
diff --git a/include/proxy/PreTransactionLogData.h b/include/proxy/PreTransactionLogData.h
deleted file mode 100644
index 9d65e667616..00000000000
--- a/include/proxy/PreTransactionLogData.h
+++ /dev/null
@@ -1,218 +0,0 @@
-/** @file
-
- PreTransactionLogData populates LogData for requests that fail before
- HttpSM creation.
-
- @section license License
-
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you 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.
- */
-
-#pragma once
-
-#include "proxy/logging/TransactionLogData.h"
-
-#include
-
-/** Populate LogData for requests that never created an HttpSM.
- *
- * Malformed HTTP/2 or HTTP/3 request headers can be rejected while the
- * connection is still decoding and validating the stream, before the request
- * progresses far enough to create an HttpSM. This class carries the
- * copied request and session metadata needed to emit a best-effort
- * transaction log entry for those failures.
- *
- * Unlike TransactionLogData (which reads from a live HttpSM), this class
- * owns its milestones, addresses, and strings because the originating
- * stream is about to be destroyed.
- */
-class PreTransactionLogData : public TransactionLogData
-{
-public:
- PreTransactionLogData() = default;
-
- ~PreTransactionLogData() override
- {
- if (owned_client_request.valid()) {
- owned_client_request.destroy();
- }
- }
-
- // ===== Milestones =====
-
- TransactionMilestones const *
- get_milestones() const override
- {
- return &owned_milestones;
- }
-
- // ===== Headers =====
-
- HTTPHdr *
- get_client_request() const override
- {
- if (owned_client_request.valid()) {
- return const_cast(&owned_client_request);
- }
- return nullptr;
- }
-
- // ===== Client request URL / path =====
-
- const char *
- get_client_req_url_str() const override
- {
- return owned_url.empty() ? nullptr : owned_url.c_str();
- }
- int
- get_client_req_url_len() const override
- {
- return static_cast(owned_url.size());
- }
- const char *
- get_client_req_url_path_str() const override
- {
- return owned_path.empty() ? nullptr : owned_path.c_str();
- }
- int
- get_client_req_url_path_len() const override
- {
- return static_cast(owned_path.size());
- }
-
- // ===== Client addressing =====
-
- sockaddr const *
- get_client_addr() const override
- {
- return &owned_client_addr.sa;
- }
- sockaddr const *
- get_client_src_addr() const override
- {
- return &owned_client_src_addr.sa;
- }
- sockaddr const *
- get_client_dst_addr() const override
- {
- return &owned_client_dst_addr.sa;
- }
- uint16_t
- get_client_port() const override
- {
- return m_client_port;
- }
-
- // ===== Squid codes =====
-
- SquidLogCode
- get_log_code() const override
- {
- return m_log_code;
- }
- SquidHitMissCode
- get_hit_miss_code() const override
- {
- return m_hit_miss_code;
- }
- SquidHierarchyCode
- get_hier_code() const override
- {
- return m_hier_code;
- }
-
- // ===== Transaction identifiers =====
-
- int64_t
- get_connection_id() const override
- {
- return m_connection_id;
- }
- int
- get_transaction_id() const override
- {
- return m_transaction_id;
- }
-
- // ===== Protocol info =====
-
- const char *
- get_client_protocol() const override
- {
- return owned_client_protocol_str.empty() ? nullptr : owned_client_protocol_str.c_str();
- }
-
- // ===== Connection flags =====
-
- bool
- get_client_connection_is_ssl() const override
- {
- return m_client_connection_is_ssl;
- }
-
- // ===== Server transaction count =====
-
- int64_t
- get_server_transact_count() const override
- {
- return m_server_transact_count;
- }
-
- // ===== Fallback fields for pre-transaction logging =====
-
- std::string_view
- get_method() const override
- {
- return owned_method;
- }
- std::string_view
- get_scheme() const override
- {
- return owned_scheme;
- }
- std::string_view
- get_client_protocol_str() const override
- {
- return owned_client_protocol_str;
- }
-
- // ===== Owned backing storage (public for ProxyTransaction to populate). =====
-
- HTTPHdr owned_client_request;
- TransactionMilestones owned_milestones;
- IpEndpoint owned_client_addr = {};
- IpEndpoint owned_client_src_addr = {};
- IpEndpoint owned_client_dst_addr = {};
-
- std::string owned_method;
- std::string owned_scheme;
- std::string owned_authority;
- std::string owned_path;
- std::string owned_url;
- std::string owned_client_protocol_str;
-
- // ===== Simple fields (public for ProxyTransaction to set). =====
-
- uint16_t m_client_port = 0;
- SquidLogCode m_log_code = SquidLogCode::EMPTY;
- SquidHitMissCode m_hit_miss_code = SQUID_MISS_NONE;
- SquidHierarchyCode m_hier_code = SquidHierarchyCode::NONE;
- int64_t m_connection_id = 0;
- int m_transaction_id = 0;
- bool m_client_connection_is_ssl = false;
- int64_t m_server_transact_count = 0;
-};
diff --git a/include/proxy/ProxyTransaction.h b/include/proxy/ProxyTransaction.h
index 7316be293a2..32b24c6b5bc 100644
--- a/include/proxy/ProxyTransaction.h
+++ b/include/proxy/ProxyTransaction.h
@@ -146,18 +146,19 @@ class ProxyTransaction : public VConnection
void mark_as_tunnel_endpoint() override;
- /** Emit a best-effort access log entry for a request that failed before
- * HttpSM creation.
+ /** Emit a best-effort access log entry for a request without an HttpSM.
*
* Call this when a malformed request is rejected at the protocol layer
* (e.g. during HTTP/2 or HTTP/3 header decoding) and no HttpSM was
- * created. The method populates a PreTransactionLogData from the
+ * created. The method populates a NonHttpSmLogData from the
* session and the partially decoded request, then invokes Log::access.
+ * If an HttpSM exists, callers should use the normal transaction logging
+ * path instead.
*
* @param[in] request The decoded (possibly partial) request header.
* @param[in] protocol_str Protocol string for the log entry (e.g. "http/2").
*/
- void log_pre_transaction_access(HTTPHdr const *request, const char *protocol_str);
+ void log_non_http_sm_access(HTTPHdr const *request, const char *protocol_str);
/// Variables
//
diff --git a/include/proxy/hdrs/HTTP.h b/include/proxy/hdrs/HTTP.h
index 104743e593e..571bd0fdc5e 100644
--- a/include/proxy/hdrs/HTTP.h
+++ b/include/proxy/hdrs/HTTP.h
@@ -1097,7 +1097,7 @@ inline void
HTTPHdr::status_set(HTTPStatus status)
{
ink_assert(valid());
- ink_assert(m_http->m_polarity == HTTPType::RESPONSE);
+ ink_release_assert(m_http->m_polarity == HTTPType::RESPONSE);
http_hdr_status_set(m_http, status);
}
@@ -1121,7 +1121,7 @@ inline void
HTTPHdr::reason_set(std::string_view value)
{
ink_assert(valid());
- ink_assert(m_http->m_polarity == HTTPType::RESPONSE);
+ ink_release_assert(m_http->m_polarity == HTTPType::RESPONSE);
http_hdr_reason_set(m_heap, m_http, value, true);
}
diff --git a/include/proxy/hdrs/HuffmanCodec.h b/include/proxy/hdrs/HuffmanCodec.h
index e979bdc3151..9d1aa7316f4 100644
--- a/include/proxy/hdrs/HuffmanCodec.h
+++ b/include/proxy/hdrs/HuffmanCodec.h
@@ -25,5 +25,15 @@
#include
+/** Decode a Huffman-encoded string per RFC 7541 section 5.2.
+
+ @return The decoded length, or a negative value on invalid input or
+ insufficient destination space.
+
+ @note dst_len must be strictly greater than the decoded length; with an
+ exactly-sized destination the decoder may report insufficient space.
+ Huffman expands to at most 8/5 of the encoded length, so sizing dst at
+ 2x src_len always suffices (see xpack_decode_string).
+ */
int64_t huffman_decode(char *dst, uint32_t dst_len, uint8_t const *src, uint32_t src_len);
int64_t huffman_encode(uint8_t *dst, uint32_t dst_len, uint8_t const *src, uint32_t src_len);
diff --git a/include/proxy/http/CompletedTransactionLogData.h b/include/proxy/http/CompletedTransactionLogData.h
deleted file mode 100644
index e8593d42208..00000000000
--- a/include/proxy/http/CompletedTransactionLogData.h
+++ /dev/null
@@ -1,208 +0,0 @@
-/** @file
-
- CompletedTransactionLogData populates TransactionLogData from a live HttpSM.
-
- @section license License
-
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you 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.
- */
-
-#pragma once
-
-#include "proxy/logging/TransactionLogData.h"
-
-class HttpSM;
-
-/** Provide TransactionLogData from a live HttpSM via virtual getters.
- *
- * Each getter reads directly from m_http_sm on demand, avoiding the need to
- * copy all fields upfront. The HttpSM must outlive this object.
- */
-class CompletedTransactionLogData : public TransactionLogData
-{
-public:
- /** Construct from a live HttpSM.
- *
- * @param[in] sm The HttpSM for the completing transaction.
- */
- explicit CompletedTransactionLogData(HttpSM *sm);
-
- void *http_sm_for_plugins() const override;
-
- // ===== Milestones =====
- TransactionMilestones const *get_milestones() const override;
-
- // ===== Headers =====
- HTTPHdr *get_client_request() const override;
- HTTPHdr *get_proxy_response() const override;
- HTTPHdr *get_proxy_request() const override;
- HTTPHdr *get_server_response() const override;
- HTTPHdr *get_cache_response() const override;
-
- // ===== Client request URL / path =====
- const char *get_client_req_url_str() const override;
- int get_client_req_url_len() const override;
- const char *get_client_req_url_path_str() const override;
- int get_client_req_url_path_len() const override;
-
- // ===== Proxy response content-type / reason =====
- char *get_proxy_resp_content_type_str() const override;
- int get_proxy_resp_content_type_len() const override;
- char *get_proxy_resp_reason_phrase_str() const override;
- int get_proxy_resp_reason_phrase_len() const override;
-
- // ===== Unmapped URL =====
- char *get_unmapped_url_str() const override;
- int get_unmapped_url_len() const override;
-
- // ===== Cache lookup URL =====
- char *get_cache_lookup_url_str() const override;
- int get_cache_lookup_url_len() const override;
-
- // ===== Client addressing =====
- sockaddr const *get_client_addr() const override;
- sockaddr const *get_client_src_addr() const override;
- sockaddr const *get_client_dst_addr() const override;
- sockaddr const *get_verified_client_addr() const override;
- uint16_t get_client_port() const override;
-
- // ===== Server addressing =====
- sockaddr const *get_server_src_addr() const override;
- sockaddr const *get_server_dst_addr() const override;
- sockaddr const *get_server_info_dst_addr() const override;
- const char *get_server_name() const override;
-
- // ===== Squid codes =====
- SquidLogCode get_log_code() const override;
- SquidSubcode get_subcode() const override;
- SquidHitMissCode get_hit_miss_code() const override;
- SquidHierarchyCode get_hier_code() const override;
-
- // ===== Byte counters =====
- int64_t get_client_request_body_bytes() const override;
- int64_t get_client_response_hdr_bytes() const override;
- int64_t get_client_response_body_bytes() const override;
- int64_t get_server_request_body_bytes() const override;
- int64_t get_server_response_body_bytes() const override;
- int64_t get_cache_response_body_bytes() const override;
- int64_t get_cache_response_hdr_bytes() const override;
-
- // ===== Transaction identifiers =====
- int64_t get_sm_id() const override;
- int64_t get_connection_id() const override;
- int get_transaction_id() const override;
- int get_transaction_priority_weight() const override;
- int get_transaction_priority_dependence() const override;
-
- // ===== Plugin info =====
- int64_t get_plugin_id() const override;
- const char *get_plugin_tag() const override;
-
- // ===== Protocol info =====
- const char *get_client_protocol() const override;
- const char *get_server_protocol() const override;
- const char *get_client_sec_protocol() const override;
- const char *get_client_cipher_suite() const override;
- const char *get_client_curve() const override;
- const char *get_client_security_group() const override;
- int get_client_alpn_id() const override;
-
- // ===== SNI =====
- const char *get_sni_server_name() const override;
-
- // ===== Connection flags =====
- bool get_client_tcp_reused() const override;
- bool get_client_connection_is_ssl() const override;
- bool get_client_ssl_reused() const override;
- bool get_is_internal() const override;
- bool get_server_connection_is_ssl() const override;
- bool get_server_ssl_reused() const override;
- int get_server_connection_provided_cert() const override;
- int get_client_provided_cert() const override;
-
- // ===== Server transaction count =====
- int64_t get_server_transact_count() const override;
-
- // ===== Finish status =====
- int get_client_finish_status_code() const override;
- int get_proxy_finish_status_code() const override;
-
- // ===== Error codes =====
- const char *get_client_rx_error_code() const override;
- const char *get_client_tx_error_code() const override;
-
- // ===== MPTCP =====
- std::optional get_mptcp_state() const override;
-
- // ===== Misc transaction state =====
- in_port_t get_incoming_port() const override;
- int get_orig_scheme() const override;
- int64_t get_congestion_control_crat() const override;
-
- // ===== Cache state =====
- int get_cache_write_code() const override;
- int get_cache_transform_write_code() const override;
- int get_cache_open_read_tries() const override;
- int get_cache_open_write_tries() const override;
- int get_max_cache_open_write_retries() const override;
-
- // ===== Retry attempts =====
- int64_t get_simple_retry_attempts() const override;
- int64_t get_unavailable_retry_attempts() const override;
- int64_t get_retry_attempts_saved() const override;
-
- // ===== Status plugin entry name =====
- std::string_view get_http_return_code_setter_name() const override;
-
- // ===== Proxy Protocol =====
- int get_pp_version() const override;
- sockaddr const *get_pp_src_addr() const override;
- sockaddr const *get_pp_dst_addr() const override;
- std::string_view get_pp_authority() const override;
- std::string_view get_pp_tls_cipher() const override;
- std::string_view get_pp_tls_version() const override;
- std::string_view get_pp_tls_group() const override;
-
- // ===== Server response Transfer-Encoding =====
- std::string_view get_server_response_transfer_encoding() const override;
-
-private:
- HttpSM *m_http_sm;
-
- // Cached values for fields that require computation or string formatting.
- mutable char m_client_rx_error_code[10] = {'-', '\0'};
- mutable char m_client_tx_error_code[10] = {'-', '\0'};
- mutable bool m_error_codes_formatted = false;
-
- // Cached URL string pointers (computed on first access).
- mutable const char *m_client_req_url_str = nullptr;
- mutable int m_client_req_url_len = 0;
- mutable const char *m_client_req_url_path_str = nullptr;
- mutable int m_client_req_url_path_len = 0;
- mutable bool m_url_cached = false;
-
- // Cached content-type pointers (computed on first access).
- mutable char *m_proxy_resp_content_type_str = nullptr;
- mutable int m_proxy_resp_content_type_len = 0;
- mutable char *m_proxy_resp_reason_phrase_str = nullptr;
- mutable int m_proxy_resp_reason_phrase_len = 0;
- mutable bool m_content_type_cached = false;
-
- void cache_url_strings() const;
- void cache_content_type() const;
- void format_error_codes() const;
-};
diff --git a/include/proxy/http/HttpCacheSM.h b/include/proxy/http/HttpCacheSM.h
index 41623103d65..b1379e0555e 100644
--- a/include/proxy/http/HttpCacheSM.h
+++ b/include/proxy/http/HttpCacheSM.h
@@ -129,6 +129,12 @@ class HttpCacheSM : public Continuation
return cache_read_vc ? (cache_read_vc->is_compressed_in_ram()) : false;
}
+ const HttpCacheKey &
+ get_cache_key() const
+ {
+ return cache_key;
+ }
+
void
set_open_read_tries(int value)
{
diff --git a/include/proxy/http/HttpConfig.h b/include/proxy/http/HttpConfig.h
index 05a7cc511f3..a0e7ed7d9b7 100644
--- a/include/proxy/http/HttpConfig.h
+++ b/include/proxy/http/HttpConfig.h
@@ -706,11 +706,11 @@ struct OverridableHttpConfigParams {
////////////////////////////////////
// origin server connect attempts //
////////////////////////////////////
- MgmtInt connect_attempts_max_retries = 0;
- MgmtInt connect_attempts_max_retries_down_server = 3;
- MgmtInt connect_attempts_rr_retries = 3;
- MgmtInt connect_attempts_timeout = 30;
- MgmtInt connect_attempts_retry_backoff_base = 0;
+ MgmtInt connect_attempts_max_retries = 0;
+ MgmtInt connect_attempts_max_retries_suspect_server = 1;
+ MgmtInt connect_attempts_rr_retries = 3;
+ MgmtInt connect_attempts_timeout = 30;
+ MgmtInt connect_attempts_retry_backoff_base = 0;
MgmtInt connect_down_policy = 2;
@@ -773,6 +773,7 @@ struct OverridableHttpConfigParams {
char *ssl_client_cert_filename = nullptr;
char *ssl_client_private_key_filename = nullptr;
char *ssl_client_ca_cert_filename = nullptr;
+ char *ssl_client_ca_cert_path = nullptr;
char *ssl_client_alpn_protocols = nullptr;
// Host Resolution order
@@ -1031,6 +1032,7 @@ inline HttpConfigParams::~HttpConfigParams()
ats_free(oride.ssl_client_cert_filename);
ats_free(oride.ssl_client_private_key_filename);
ats_free(oride.ssl_client_ca_cert_filename);
+ ats_free(oride.ssl_client_ca_cert_path);
ats_free(connect_ports_string);
ats_free(reverse_proxy_no_host_redirect);
ats_free(redirect_actions_string);
diff --git a/include/proxy/http/HttpTransact.h b/include/proxy/http/HttpTransact.h
index a6d48a12f7f..b918fa0c27e 100644
--- a/include/proxy/http/HttpTransact.h
+++ b/include/proxy/http/HttpTransact.h
@@ -1003,7 +1003,6 @@ class HttpTransact
static void Forbidden(State *s);
static void SelfLoop(State *s);
static void TooEarly(State *s);
- static void OriginDown(State *s);
static void PostActiveTimeoutResponse(State *s);
static void PostInactiveTimeoutResponse(State *s);
static void DecideCacheLookup(State *s);
@@ -1031,10 +1030,8 @@ class HttpTransact
static void handle_transform_ready(State *s);
static void handle_transform_cache_write(State *s);
static void handle_response_from_parent(State *s);
- static void handle_response_from_parent_plugin(State *s);
static void handle_response_from_server(State *s);
- static void delete_server_rr_entry(State *s, int max_retries);
- static void retry_server_connection_not_open(State *s, ServerState_t conn_state, unsigned max_retries);
+ static void retry_server_connection_not_open(State *s, unsigned max_retries);
static void error_log_connection_failure(State *s, ServerState_t conn_state);
static void handle_server_connection_not_open(State *s);
static void handle_forward_server_connection_open(State *s);
@@ -1078,12 +1075,13 @@ class HttpTransact
static bool handle_trace_and_options_requests(State *s, HTTPHdr *incoming_hdr);
static void bootstrap_state_variables_from_request(State *s, HTTPHdr *incoming_request);
+ static uint8_t origin_server_connect_attempts_max_retries(State *s);
+
// WARNING: this function may be called multiple times for the same transaction.
//
static void initialize_state_variables_from_request(State *s, HTTPHdr *obsolete_incoming_request);
static void initialize_state_variables_from_response(State *s, HTTPHdr *incoming_response);
- static bool is_server_negative_cached(State *s);
static bool is_cache_response_returnable(State *s);
static bool is_stale_cache_response_returnable(State *s);
static bool need_to_revalidate(State *s);
diff --git a/include/proxy/http/HttpUserAgent.h b/include/proxy/http/HttpUserAgent.h
index 4761e4a30e4..95b1120545d 100644
--- a/include/proxy/http/HttpUserAgent.h
+++ b/include/proxy/http/HttpUserAgent.h
@@ -31,6 +31,7 @@
#include "proxy/ProxyTransaction.h"
#include "records/RecHttp.h"
#include "iocore/net/TLSBasicSupport.h"
+#include "iocore/net/TLSEarlyDataSupport.h"
#include "iocore/net/TLSSessionResumptionSupport.h"
#include "tscore/ink_assert.h"
@@ -55,6 +56,11 @@ struct ClientConnectionInfo {
std::string security_group{"-"};
int alpn_id{SessionProtocolNameRegistry::INVALID};
+
+ // TLS handshake bytes (rx = received from client, tx = sent to client)
+ uint64_t tls_handshake_bytes_rx{0};
+ uint64_t tls_handshake_bytes_tx{0};
+ size_t tls_early_data_len{0};
};
class HttpUserAgent
@@ -97,6 +103,12 @@ class HttpUserAgent
int get_client_alpn_id() const;
+ uint64_t get_client_tls_handshake_bytes_rx() const;
+
+ uint64_t get_client_tls_handshake_bytes_tx() const;
+
+ size_t get_client_tls_early_data_len() const;
+
private:
HttpVCTableEntry *m_entry{nullptr};
IOBufferReader *m_raw_buffer_reader{nullptr};
@@ -186,6 +198,11 @@ HttpUserAgent::set_txn(ProxyTransaction *txn, TransactionMilestones &milestones)
milestones[TS_MILESTONE_TLS_HANDSHAKE_START] = tbs->get_tls_handshake_begin_time();
milestones[TS_MILESTONE_TLS_HANDSHAKE_END] = tbs->get_tls_handshake_end_time();
}
+ tbs->get_tls_handshake_bytes(m_conn_info.tls_handshake_bytes_rx, m_conn_info.tls_handshake_bytes_tx);
+ }
+
+ if (auto eds = netvc->get_service()) {
+ m_conn_info.tls_early_data_len = eds->get_early_data_len();
}
if (auto as = netvc->get_service()) {
@@ -301,6 +318,24 @@ HttpUserAgent::get_client_alpn_id() const
return m_conn_info.alpn_id;
}
+inline uint64_t
+HttpUserAgent::get_client_tls_handshake_bytes_rx() const
+{
+ return m_conn_info.tls_handshake_bytes_rx;
+}
+
+inline uint64_t
+HttpUserAgent::get_client_tls_handshake_bytes_tx() const
+{
+ return m_conn_info.tls_handshake_bytes_tx;
+}
+
+inline size_t
+HttpUserAgent::get_client_tls_early_data_len() const
+{
+ return m_conn_info.tls_early_data_len;
+}
+
inline void
HttpUserAgent::save_transaction_info()
{
diff --git a/include/proxy/http/OverridableConfigDefs.h b/include/proxy/http/OverridableConfigDefs.h
index d70c4c54caa..bf00fc3def9 100644
--- a/include/proxy/http/OverridableConfigDefs.h
+++ b/include/proxy/http/OverridableConfigDefs.h
@@ -160,7 +160,8 @@
X(HTTP_TRANSACTION_NO_ACTIVITY_TIMEOUT_OUT, transaction_no_activity_timeout_out, "proxy.config.http.transaction_no_activity_timeout_out", INT, GENERIC) \
X(HTTP_TRANSACTION_ACTIVE_TIMEOUT_OUT, transaction_active_timeout_out, "proxy.config.http.transaction_active_timeout_out", INT, GENERIC) \
X(HTTP_CONNECT_ATTEMPTS_MAX_RETRIES, connect_attempts_max_retries, "proxy.config.http.connect_attempts_max_retries", INT, GENERIC) \
- X(HTTP_CONNECT_ATTEMPTS_MAX_RETRIES_DOWN_SERVER, connect_attempts_max_retries_down_server, "proxy.config.http.connect_attempts_max_retries_down_server", INT, GENERIC) \
+ X(HTTP_CONNECT_ATTEMPTS_MAX_RETRIES_DOWN_SERVER, connect_attempts_max_retries_suspect_server, "proxy.config.http.connect_attempts_max_retries_down_server", INT, GENERIC) \
+ X(HTTP_CONNECT_ATTEMPTS_MAX_RETRIES_SUSPECT_SERVER, connect_attempts_max_retries_suspect_server, "proxy.config.http.connect_attempts_max_retries_suspect_server", INT, GENERIC) \
X(HTTP_CONNECT_ATTEMPTS_RR_RETRIES, connect_attempts_rr_retries, "proxy.config.http.connect_attempts_rr_retries", INT, GENERIC) \
X(HTTP_CONNECT_ATTEMPTS_TIMEOUT, connect_attempts_timeout, "proxy.config.http.connect_attempts_timeout", INT, GENERIC) \
X(HTTP_DOWN_SERVER_CACHE_TIME, down_server_timeout, "proxy.config.http.down_server.cache_time", INT, HttpDownServerCacheTimeConv) \
@@ -250,6 +251,7 @@
X(HTTP_CONNECT_ATTEMPTS_RETRY_BACKOFF_BASE, connect_attempts_retry_backoff_base, "proxy.config.http.connect_attempts_retry_backoff_base", INT, GENERIC) \
X(HTTP_NEGATIVE_REVALIDATING_LIST, negative_revalidating_list, "proxy.config.http.negative_revalidating_list", STRING, HttpStatusCodeList_Conv) \
X(HTTP_CACHE_POST_METHOD, cache_post_method, "proxy.config.http.cache.post_method", INT, GENERIC) \
- X(HTTP_CACHE_TARGETED_CACHE_CONTROL_HEADERS, targeted_cache_control_headers, "proxy.config.http.cache.targeted_cache_control_headers", STRING, TargetedCacheControlHeaders_Conv)
+ X(HTTP_CACHE_TARGETED_CACHE_CONTROL_HEADERS, targeted_cache_control_headers, "proxy.config.http.cache.targeted_cache_control_headers", STRING, TargetedCacheControlHeaders_Conv) \
+ X(SSL_CLIENT_CA_CERT_PATH, ssl_client_ca_cert_path, "proxy.config.ssl.client.CA.cert.path", STRING, NONE)
// clang-format on
diff --git a/include/proxy/http/remap/PluginDso.h b/include/proxy/http/remap/PluginDso.h
index d3ea8087a2b..68b01f90588 100644
--- a/include/proxy/http/remap/PluginDso.h
+++ b/include/proxy/http/remap/PluginDso.h
@@ -46,17 +46,14 @@
namespace fs = swoc::file;
#include "tscore/Ptr.h"
+#include "tsutil/Metrics.h"
#include "iocore/eventsystem/EventSystem.h"
#include "proxy/Plugin.h"
+#include "proxy/PluginThreadContext.h"
-class PluginThreadContext : public RefCountObjInHeap
-{
-public:
- virtual void acquire() = 0;
- virtual void release() = 0;
- static constexpr const char *const _tag = "plugin_context"; /** @brief log tag used by this class */
-};
+#include
+#include
class PluginDso : public PluginThreadContext
{
diff --git a/include/proxy/http/remap/RemapConfig.h b/include/proxy/http/remap/RemapConfig.h
index 129d619f26f..d745a4d93b0 100644
--- a/include/proxy/http/remap/RemapConfig.h
+++ b/include/proxy/http/remap/RemapConfig.h
@@ -23,6 +23,7 @@
#pragma once
+#include "mgmt/config/ConfigContext.h"
#include "proxy/http/remap/AclFiltering.h"
class UrlRewrite;
@@ -80,7 +81,7 @@ struct BUILD_TABLE_INFO {
};
const char *remap_parse_directive(BUILD_TABLE_INFO *bti, char *errbuf, size_t errbufsize);
-bool remap_parse_config_bti(const char *path, BUILD_TABLE_INFO *bti);
+bool remap_parse_config_bti(const char *path, BUILD_TABLE_INFO *bti, ConfigContext ctx = {});
const char *remap_validate_filter_args(acl_filter_rule **rule_pp, const char *const *argv, int argc, char *errStrBuf,
size_t errStrBufSize, ACLBehaviorPolicy behavior_policy);
@@ -88,7 +89,7 @@ const char *remap_validate_filter_args(acl_filter_rule **rule_pp, const char *co
unsigned long remap_check_option(const char *const *argv, int argc, unsigned long findmode = 0, int *_ret_idx = nullptr,
const char **argptr = nullptr);
-bool remap_parse_config(const char *path, UrlRewrite *rewrite);
+bool remap_parse_config(const char *path, UrlRewrite *rewrite, ConfigContext ctx = {});
using load_remap_file_func = void (*)(const char *, const char *);
diff --git a/include/proxy/http/remap/RemapYamlConfig.h b/include/proxy/http/remap/RemapYamlConfig.h
index a5ec4919c0a..a93ee9d8b10 100644
--- a/include/proxy/http/remap/RemapYamlConfig.h
+++ b/include/proxy/http/remap/RemapYamlConfig.h
@@ -30,6 +30,7 @@
#include
#include "swoc/Errata.h"
+#include "mgmt/config/ConfigContext.h"
#include "proxy/http/remap/RemapConfig.h"
#include "proxy/hdrs/URL.h"
@@ -68,6 +69,6 @@ swoc::Errata parse_yaml_include_directive(const std::string &include_path, BUILD
swoc::Errata parse_yaml_remap_rule(const YAML::Node &node, BUILD_TABLE_INFO *bti);
// Parse remap YAML node
-bool remap_parse_yaml_bti(const char *path, BUILD_TABLE_INFO *bti);
+bool remap_parse_yaml_bti(const char *path, BUILD_TABLE_INFO *bti, ConfigContext ctx = {});
-bool remap_parse_yaml(const char *path, UrlRewrite *rewrite);
+bool remap_parse_yaml(const char *path, UrlRewrite *rewrite, ConfigContext ctx = {});
diff --git a/include/proxy/http/remap/UrlRewrite.h b/include/proxy/http/remap/UrlRewrite.h
index 2f4be91b479..2ef85b9ed70 100644
--- a/include/proxy/http/remap/UrlRewrite.h
+++ b/include/proxy/http/remap/UrlRewrite.h
@@ -25,6 +25,7 @@
#pragma once
#include "iocore/eventsystem/Freer.h"
+#include "mgmt/config/ConfigContext.h"
#include "proxy/http/remap/UrlMapping.h"
#include "proxy/http/remap/UrlMappingPathIndex.h"
#include "proxy/http/HttpTransact.h"
@@ -77,14 +78,15 @@ class UrlRewrite : public RefCountObjInHeap
*
* @return @c true if the instance state is valid, @c false if not.
*/
- bool load();
+ bool load(ConfigContext ctx = {});
/** Build the internal url write tables.
*
* @param path Path to configuration file.
+ * @param ctx ConfigContext for reload status tracking.
* @return 0 on success, non-zero error code on failure.
*/
- int BuildTable(const char *path);
+ int BuildTable(const char *path, ConfigContext ctx = {});
mapping_type Remap_redirect(HTTPHdr *request_header, URL *redirect_url);
bool ReverseMap(HTTPHdr *response_header);
diff --git a/include/proxy/logging/Log.h b/include/proxy/logging/Log.h
index 1a7c81114f9..fffc8adf2f2 100644
--- a/include/proxy/logging/Log.h
+++ b/include/proxy/logging/Log.h
@@ -43,7 +43,7 @@
@section example Example usage of the API
@code
- // Populate a LogData (e.g. TransactionLogData or PreTransactionLogData), then:
+ // Populate a TransactionLogData source, then:
LogAccess entry(data);
int ret = Log::access(&entry);
@endcode
diff --git a/include/proxy/logging/LogAccess.h b/include/proxy/logging/LogAccess.h
index 7701944df5c..35f14ea55c5 100644
--- a/include/proxy/logging/LogAccess.h
+++ b/include/proxy/logging/LogAccess.h
@@ -123,8 +123,8 @@ class LogAccess
* The caller retains ownership of @a data, which must outlive the
* synchronous Log::access() call that marshals this entry.
*
- * @param[in] data Populated TransactionLogData (CompletedTransactionLogData
- * or PreTransactionLogData).
+ * @param[in] data Populated TransactionLogData for an HttpSM-backed or
+ * non-HttpSM entry.
*/
explicit LogAccess(TransactionLogData &data);
@@ -156,11 +156,13 @@ class LogAccess
int marshal_client_req_protocol_version(char *); // STR
int marshal_server_req_protocol_version(char *); // STR
int marshal_client_req_squid_len(char *); // INT
+ int marshal_client_req_squid_len_tls(char *); // INT
int marshal_client_req_header_len(char *); // INT
int marshal_client_req_content_len(char *); // INT
int marshal_client_req_tcp_reused(char *); // INT
int marshal_client_req_is_ssl(char *); // INT
int marshal_client_req_ssl_reused(char *); // INT
+ int marshal_client_ssl_resumption_type(char *); // INT
int marshal_client_req_is_internal(char *); // INT
int marshal_client_req_mptcp_state(char *); // INT
int marshal_client_security_protocol(char *); // STR
@@ -173,6 +175,9 @@ class LogAccess
int marshal_client_req_uuid(char *); // STR
int marshal_client_rx_error_code(char *); // STR
int marshal_client_tx_error_code(char *); // STR
+ int marshal_client_tls_handshake_bytes_rx(char *); // INT
+ int marshal_client_tls_handshake_bytes_tx(char *); // INT
+ int marshal_client_tls_handshake_bytes(char *); // INT
int marshal_client_req_all_header_fields(char *); // STR
//
@@ -181,6 +186,7 @@ class LogAccess
int marshal_proxy_resp_content_type(char *); // STR
int marshal_proxy_resp_reason_phrase(char *); // STR
int marshal_proxy_resp_squid_len(char *); // INT
+ int marshal_proxy_resp_squid_len_tls(char *); // INT
int marshal_proxy_resp_content_len(char *); // INT
int marshal_proxy_resp_status_code(char *); // INT
int marshal_status_plugin_entry(char *); // STR
@@ -254,6 +260,7 @@ class LogAccess
//
int marshal_cache_write_code(char *); // INT
int marshal_cache_write_transform_code(char *); // INT
+ int marshal_cache_key_hash(char *); // STR
// other fields
//
@@ -310,10 +317,6 @@ class LogAccess
//
int marshal_milestone(TSMilestonesType ms, char *buf);
int marshal_milestone_fmt_sec(TSMilestonesType ms, char *buf);
- int marshal_milestone_fmt_squid(TSMilestonesType ms, char *buf);
- int marshal_milestone_fmt_netscape(TSMilestonesType ms, char *buf);
- int marshal_milestone_fmt_date(TSMilestonesType ms, char *buf);
- int marshal_milestone_fmt_time(TSMilestonesType ms, char *buf);
int marshal_milestone_fmt_ms(TSMilestonesType ms, char *buf);
int marshal_milestone_diff(TSMilestonesType ms1, TSMilestonesType ms2, char *buf);
int marshal_milestones_csv(char *buf);
@@ -322,7 +325,7 @@ class LogAccess
void set_http_header_field(LogField::Container container, char *field, char *buf, int len);
// Plugin
- int marshal_custom_field(char *buf, const LogField::CustomMarshalFunc &plugin_marshal_func);
+ int marshal_custom_field(char *buf, LogField::Type type, const LogField::CustomMarshalFunc &plugin_marshal_func);
//
// unmarshalling routines
@@ -342,7 +345,6 @@ class LogAccess
static int unmarshal_int_to_time_str(char **buf, char *dest, int len);
static int unmarshal_int_to_netscape_str(char **buf, char *dest, int len);
static int unmarshal_http_version(char **buf, char *dest, int len);
- static int unmarshal_http_text(char **buf, char *dest, int len, LogSlice *slice, LogEscapeType escape_type);
static int unmarshal_http_status(char **buf, char *dest, int len);
static int unmarshal_ip(char **buf, IpEndpoint *dest);
static int unmarshal_ip_to_str(char **buf, char *dest, int len);
@@ -352,7 +354,6 @@ class LogAccess
static int unmarshal_cache_code(char **buf, char *dest, int len, const Ptr &map);
static int unmarshal_cache_hit_miss(char **buf, char *dest, int len, const Ptr &map);
static int unmarshal_cache_write_code(char **buf, char *dest, int len, const Ptr &map);
- static int unmarshal_client_protocol_stack(char **buf, char *dest, int len, Ptr map);
static int unmarshal_with_map(int64_t code, char *dest, int len, const Ptr &map, const char *msg = nullptr);
diff --git a/include/proxy/logging/LogBuffer.h b/include/proxy/logging/LogBuffer.h
index 4f8295ae444..f7e223a0802 100644
--- a/include/proxy/logging/LogBuffer.h
+++ b/include/proxy/logging/LogBuffer.h
@@ -30,12 +30,17 @@
#include "proxy/logging/LogLimits.h"
#include "proxy/logging/LogAccess.h"
+#include
+
class LogObject;
class LogConfig;
class LogBufferIterator;
-#define LOG_SEGMENT_COOKIE 0xaceface
-#define LOG_SEGMENT_VERSION 2
+#define LOG_SEGMENT_COOKIE 0xaceface
+
+#define LOG_SEGMENT_VERSION 3 ///< Current default version.
+#define LOG_SEGMENT_VERSION_MIN_SUPPORTED 2 ///< Oldest version this build can still read.
+#define LOG_SEGMENT_VERSION_FIELDTYPES 3 ///< First version that carries the field-type schema (self-describing).
#if defined(__linux__)
#define LB_DEFAULT_ALIGN 512
@@ -56,6 +61,35 @@ struct LogEntryHeader {
uint32_t entry_len;
};
+/*-------------------------------------------------------------------------
+ LogFieldTypeSchema
+
+ Self-describing field-type table for v3 segments, written once per segment at
+ LogBufferHeader::fmt_fieldtypes_offset (alongside fmt_fieldlist). It lets a
+ generic reader decode every field from the file alone, dispatching on the
+ LogField::Type codes with no embedded ATS symbol->type table.
+
+ On-wire layout:
+
+ uint16_t field_count; // == number of symbols in fmt_fieldlist
+ uint8_t type_code[field_count]; // LogField::Type, in fieldlist order
+
+ No independent schema version: the segment's LOG_SEGMENT_VERSION governs this
+ layout. All integers are host byte order, like the rest of LogBufferHeader;
+ the blob is padded with the header to 8-byte alignment.
+ -------------------------------------------------------------------------*/
+
+struct LogFieldTypeSchema {
+ uint16_t field_count;
+ // Immediately followed by uint8_t type_code[field_count].
+
+ const uint8_t *
+ type_codes() const
+ {
+ return reinterpret_cast(this) + sizeof(LogFieldTypeSchema);
+ }
+};
+
/*-------------------------------------------------------------------------
LogBufferHeader
@@ -87,14 +121,52 @@ struct LogBufferHeader {
uint32_t data_offset; // offset to start of data entry
// section
+ // NEW in v3: offset to the LogFieldTypeSchema blob, or 0 if absent. Appended
+ // after data_offset so the layout through data_offset is byte-identical to
+ // v2; v2 readers ignore it and v3 readers tolerate v2 segments lacking it.
+ uint32_t fmt_fieldtypes_offset;
+
// some helper functions to return the header strings
char *fmt_fieldlist();
char *fmt_printf();
+ char *fmt_fieldtypes(); // v3 field-type schema blob; nullptr for v2 segments
char *src_hostname();
char *log_filename();
};
+/** Whether this build can read a segment of the given @a version.
+
+ The single source of truth for the supported range: readers accept the
+ inclusive range [LOG_SEGMENT_VERSION_MIN_SUPPORTED, LOG_SEGMENT_VERSION] so
+ a new build keeps decoding logs written by an older one.
+*/
+inline bool
+log_segment_version_supported(unsigned version)
+{
+ return LOG_SEGMENT_VERSION_MIN_SUPPORTED <= version && version <= LOG_SEGMENT_VERSION;
+}
+
+/** On-disk size of LogBufferHeader for a given segment version.
+
+ Raw readers that read the header by size (rather than via the data_offset
+ field) must size the read to the version on disk: v3 appended
+ fmt_fieldtypes_offset after data_offset, so a v2 segment's header is
+ shorter. Reading a v2 segment with the (larger) v3 struct size would
+ consume bytes belonging to the data section.
+
+ @return the header size in bytes, or 0 if @a version is unsupported.
+*/
+inline size_t
+log_buffer_header_size(unsigned version)
+{
+ if (!log_segment_version_supported(version)) {
+ return 0;
+ }
+ // v2 stops at data_offset; v3 and later include fmt_fieldtypes_offset.
+ return version >= LOG_SEGMENT_VERSION_FIELDTYPES ? sizeof(LogBufferHeader) : offsetof(LogBufferHeader, fmt_fieldtypes_offset);
+}
+
union LB_State {
LB_State() : ival(0) {}
LB_State(LB_State &vs) { ival = vs.ival; }
@@ -193,6 +265,7 @@ class LogBuffer
static size_t max_entry_bytes();
static int to_ascii(LogEntryHeader *entry, LogFormatType type, char *buf, int max_len, const char *symbol_str, char *printf_str,
unsigned buffer_version, const char *alt_format = nullptr, LogEscapeType escape_type = LOG_ESCAPE_NONE);
+
static int resolve_custom_entry(LogFieldList *fieldlist, char *printf_str, char *read_from, char *write_to, int write_to_len,
long timestamp, long timestamp_us, unsigned buffer_version, LogFieldList *alt_fieldlist = nullptr,
char *alt_printf_str = nullptr, LogEscapeType escape_type = LOG_ESCAPE_NONE);
@@ -239,6 +312,7 @@ class LogBuffer
// private functions
size_t _add_buffer_header(const LogConfig *cfg);
unsigned add_header_str(const char *str, char *buf_ptr, unsigned buf_len);
+ unsigned add_field_type_schema(const LogFieldList *fieldlist, char *buf_ptr, unsigned buf_len);
void freeLogBuffer();
// -- member functions that are not allowed --
@@ -296,6 +370,7 @@ class LogBufferIterator
private:
char *m_next;
+ char *m_buffer_end; // one past the last readable byte of the segment
unsigned m_iter_entry_count;
unsigned m_buffer_entry_count;
@@ -311,21 +386,30 @@ class LogBufferIterator
-------------------------------------------------------------------------*/
inline LogBufferIterator::LogBufferIterator(LogBufferHeader *header)
- : m_next(nullptr), m_iter_entry_count(0), m_buffer_entry_count(0)
+ : m_next(nullptr), m_buffer_end(nullptr), m_iter_entry_count(0), m_buffer_entry_count(0)
{
ink_assert(header);
- switch (header->version) {
- case LOG_SEGMENT_VERSION:
- m_next = (char *)header + header->data_offset;
- m_buffer_entry_count = header->entry_count;
- break;
-
- default:
+ // Entry iteration is identical across v2/v3 (v3 only appended a header field
+ // after data_offset). Accept the whole supported range.
+ if (log_segment_version_supported(header->version)) {
+ // Bound the data section against byte_count so a corrupt/hostile data_offset
+ // (a .blog read by logcat/logstats may be untrusted) can't make next()
+ // dereference outside the buffer. data_offset sits past the header, and must
+ // be 8-byte aligned so the int64 reads in each entry are well-aligned.
+ size_t header_size = log_buffer_header_size(header->version);
+ if (header->data_offset >= header_size && header->data_offset <= header->byte_count &&
+ header->data_offset % INK_MIN_ALIGN == 0) {
+ m_next = reinterpret_cast(header) + header->data_offset;
+ m_buffer_end = reinterpret_cast(header) + header->byte_count;
+ m_buffer_entry_count = header->entry_count;
+ }
+ // else: bad data_offset -- leave m_next null (next() yields nothing). The
+ // file readers report it; the iterator stays silent to avoid log spam.
+ } else {
Note("Invalid LogBuffer version %d in LogBufferIterator; "
- "current version is %d",
- header->version, LOG_SEGMENT_VERSION);
- break;
+ "supported versions are %d-%d",
+ header->version, LOG_SEGMENT_VERSION_MIN_SUPPORTED, LOG_SEGMENT_VERSION);
}
}
diff --git a/include/proxy/logging/LogConfig.h b/include/proxy/logging/LogConfig.h
index d891cf0b237..b9a44a6a2de 100644
--- a/include/proxy/logging/LogConfig.h
+++ b/include/proxy/logging/LogConfig.h
@@ -49,6 +49,7 @@ struct LogsStatsBlock {
Metrics::Counter::AtomicType *event_log_access_aggr;
Metrics::Counter::AtomicType *event_log_access_full;
Metrics::Counter::AtomicType *event_log_access_fail;
+ Metrics::Counter::AtomicType *marshalled_bytes;
Metrics::Counter::AtomicType *num_sent_to_network;
Metrics::Counter::AtomicType *num_lost_before_sent_to_network;
Metrics::Counter::AtomicType *num_received_from_network;
diff --git a/include/proxy/logging/LogField.h b/include/proxy/logging/LogField.h
index d69f675034f..1fbb3dc2168 100644
--- a/include/proxy/logging/LogField.h
+++ b/include/proxy/logging/LogField.h
@@ -95,12 +95,22 @@ class LogField
using VarUnmarshalFuncSliceOnly = std::variant;
using VarUnmarshalFunc = std::variant;
- enum Type {
- sINT = 0,
- dINT,
- STRING,
- IP, ///< IP Address.
- N_TYPES
+ /** Field value type.
+
+ These values are also the on-wire type codes of the v3 binary log schema
+ (see LogBufferHeader::fmt_fieldtypes() and the v3 format docs under
+ doc/developer-guide/logging-architecture/), so they are a published
+ contract: append before N_TYPES, never renumber. 0 is reserved for INVALID
+ so a zero-filled schema byte can't pass as a real type. The code describes
+ only framing (how to walk/skip a field), never the value's meaning.
+ */
+ enum class Type : uint8_t {
+ INVALID = 0, ///< Reserved: never written; a reader treats 0 (or any unknown code) as unframmable and stops.
+ sINT = 1, ///< one int64_t, 8 bytes (host byte order).
+ dINT = 2, ///< two int64_t (16 bytes), e.g. HTTP version major/minor.
+ STRING = 3, ///< NUL-terminated, 8-byte padded.
+ IP = 4, ///< uint16_t family + family-sized address, 8-byte padded.
+ N_TYPES = 5, ///< Internal bound (asserts / name table); NOT a wire code.
};
enum Container {
diff --git a/include/proxy/logging/LogFormat.h b/include/proxy/logging/LogFormat.h
index 15bb04c5977..431bfa4fd83 100644
--- a/include/proxy/logging/LogFormat.h
+++ b/include/proxy/logging/LogFormat.h
@@ -115,6 +115,13 @@ class LogFormat : public RefCountObjInHeap
{
return m_field_count;
}
+ // Read-only view of the parsed fields, in marshalling (on-wire) order. Used
+ // by the v3 binary log writer to publish each field's wire type.
+ const LogFieldList &
+ field_list() const
+ {
+ return m_field_list;
+ }
long
interval() const
{
diff --git a/include/proxy/logging/LogObject.h b/include/proxy/logging/LogObject.h
index 24e668e43cf..2b774aa8c5d 100644
--- a/include/proxy/logging/LogObject.h
+++ b/include/proxy/logging/LogObject.h
@@ -96,7 +96,8 @@ class LogObject : public RefCountObjInHeap
LogObject(LogConfig *cfg, const LogFormat *format, const char *log_dir, const char *basename, LogFileFormat file_format,
const char *header, Log::RollingEnabledValues rolling_enabled, int flush_threads, int rolling_interval_sec = 0,
int rolling_offset_hr = 0, int rolling_size_mb = 0, bool auto_created = false, int rolling_max_count = 0,
- int rolling_min_count = 0, bool reopen_after_rolling = false, int pipe_buffer_size = 0, bool m_fast = false);
+ int rolling_min_count = 0, bool reopen_after_rolling = false, int pipe_buffer_size = 0, bool m_fast = false,
+ unsigned binary_log_version = LOG_SEGMENT_VERSION);
~LogObject() override;
void add_filter(LogFilter *filter, bool copy = true);
@@ -219,6 +220,15 @@ class LogObject : public RefCountObjInHeap
return m_flags;
}
+ // On-disk binary segment version (logging.yaml "binary_log_version"); 2 emits
+ // the pre-v3 layout. Binary logs only; defaults to the current version. Set at
+ // construction (before the first buffer header is stamped), not afterward.
+ inline unsigned
+ get_binary_log_version() const
+ {
+ return m_binary_log_version;
+ }
+
void rename(char *new_name);
inline bool
@@ -259,8 +269,9 @@ class LogObject : public RefCountObjInHeap
// could not be used because of
// name conflicts
- unsigned int m_flags; // diverse object flags (see above)
- uint64_t m_signature; // INK_MD5 signature for object
+ unsigned int m_flags; // diverse object flags (see above)
+ uint64_t m_signature; // INK_MD5 signature for object
+ unsigned m_binary_log_version = LOG_SEGMENT_VERSION; // on-disk binary segment version (logging.yaml "binary_log_version")
Log::RollingEnabledValues m_rolling_enabled;
int m_flush_threads; // number of flush threads
diff --git a/include/proxy/logging/TransactionLogData.h b/include/proxy/logging/TransactionLogData.h
index 99248614dda..908e036e389 100644
--- a/include/proxy/logging/TransactionLogData.h
+++ b/include/proxy/logging/TransactionLogData.h
@@ -1,6 +1,6 @@
/** @file
- Base class providing the data interface for access log entries.
+ Concrete data accessor for access log entries.
@section license License
@@ -30,569 +30,200 @@
#include
#include
-class HTTPHdr;
+class HttpSM;
+class NonHttpSmLogData;
-/** Abstract base for the data backing a single access log entry.
+/** Provide access-log data from either a completed HttpSM or non-HttpSM storage.
*
- * Subclasses provide data from the appropriate source via virtual getters:
- * - CompletedTransactionLogData reads from HttpSM (defined in the http
- * module) for transactions that completed normally.
- * - PreTransactionLogData returns owned storage (defined in the proxy
- * module), for requests that never create an HttpSM.
- *
- * LogAccess reads only from this interface, so the logging module has no
- * compile-time dependency on the http module.
+ * The common completed-transaction path reads directly from @c HttpSM. The
+ * rare non-HttpSM path reads from @c NonHttpSmLogData, which owns copied
+ * request/session state for exceptional access-log entries that cannot be
+ * backed by an @c HttpSM.
*/
class TransactionLogData
{
public:
- virtual ~TransactionLogData() = default;
-
- /** Return the HttpSM pointer for plugin custom marshal functions.
- *
- * Only TransactionLogData provides a non-null value. Pre-transaction
- * entries have no HttpSM, so plugins receive nullptr.
- *
- * @return An opaque pointer to the HttpSM, or nullptr.
- */
- virtual void *
- http_sm_for_plugins() const
- {
- return nullptr;
- }
+ explicit TransactionLogData(HttpSM *sm);
+ explicit TransactionLogData(NonHttpSmLogData const &non_http_sm_data);
- // ===== Milestones =====
+ void *http_sm_for_plugins() const;
- virtual TransactionMilestones const *
- get_milestones() const
- {
- return nullptr;
- }
+ // ===== Milestones =====
+ TransactionMilestones const *get_milestones() const;
// ===== Headers =====
-
- virtual HTTPHdr *
- get_client_request() const
- {
- return nullptr;
- }
- virtual HTTPHdr *
- get_proxy_response() const
- {
- return nullptr;
- }
- virtual HTTPHdr *
- get_proxy_request() const
- {
- return nullptr;
- }
- virtual HTTPHdr *
- get_server_response() const
- {
- return nullptr;
- }
- virtual HTTPHdr *
- get_cache_response() const
- {
- return nullptr;
- }
+ HTTPHdr *get_client_request() const;
+ HTTPHdr *get_proxy_response() const;
+ HTTPHdr *get_proxy_request() const;
+ HTTPHdr *get_server_response() const;
+ HTTPHdr *get_cache_response() const;
// ===== Client request URL / path =====
-
- virtual const char *
- get_client_req_url_str() const
- {
- return nullptr;
- }
- virtual int
- get_client_req_url_len() const
- {
- return 0;
- }
- virtual const char *
- get_client_req_url_path_str() const
- {
- return nullptr;
- }
- virtual int
- get_client_req_url_path_len() const
- {
- return 0;
- }
+ const char *get_client_req_url_str() const;
+ int get_client_req_url_len() const;
+ const char *get_client_req_url_path_str() const;
+ int get_client_req_url_path_len() const;
// ===== Proxy response content-type / reason =====
-
- virtual char *
- get_proxy_resp_content_type_str() const
- {
- return nullptr;
- }
- virtual int
- get_proxy_resp_content_type_len() const
- {
- return 0;
- }
- virtual char *
- get_proxy_resp_reason_phrase_str() const
- {
- return nullptr;
- }
- virtual int
- get_proxy_resp_reason_phrase_len() const
- {
- return 0;
- }
+ char *get_proxy_resp_content_type_str() const;
+ int get_proxy_resp_content_type_len() const;
+ char *get_proxy_resp_reason_phrase_str() const;
+ int get_proxy_resp_reason_phrase_len() const;
// ===== Unmapped URL =====
-
- virtual char *
- get_unmapped_url_str() const
- {
- return nullptr;
- }
- virtual int
- get_unmapped_url_len() const
- {
- return 0;
- }
+ char *get_unmapped_url_str() const;
+ int get_unmapped_url_len() const;
// ===== Cache lookup URL =====
-
- virtual char *
- get_cache_lookup_url_str() const
- {
- return nullptr;
- }
- virtual int
- get_cache_lookup_url_len() const
- {
- return 0;
- }
+ char *get_cache_lookup_url_str() const;
+ int get_cache_lookup_url_len() const;
+ const ts::CryptoHash *get_cache_lookup_hash() const;
// ===== Client addressing =====
-
- virtual sockaddr const *
- get_client_addr() const
- {
- return nullptr;
- }
- virtual sockaddr const *
- get_client_src_addr() const
- {
- return nullptr;
- }
- virtual sockaddr const *
- get_client_dst_addr() const
- {
- return nullptr;
- }
- virtual sockaddr const *
- get_verified_client_addr() const
- {
- return nullptr;
- }
- virtual uint16_t
- get_client_port() const
- {
- return 0;
- }
+ sockaddr const *get_client_addr() const;
+ sockaddr const *get_client_src_addr() const;
+ sockaddr const *get_client_dst_addr() const;
+ sockaddr const *get_verified_client_addr() const;
+ uint16_t get_client_port() const;
// ===== Server addressing =====
-
- virtual sockaddr const *
- get_server_src_addr() const
- {
- return nullptr;
- }
- virtual sockaddr const *
- get_server_dst_addr() const
- {
- return nullptr;
- }
- virtual sockaddr const *
- get_server_info_dst_addr() const
- {
- return nullptr;
- }
- virtual const char *
- get_server_name() const
- {
- return nullptr;
- }
+ sockaddr const *get_server_src_addr() const;
+ sockaddr const *get_server_dst_addr() const;
+ sockaddr const *get_server_info_dst_addr() const;
+ const char *get_server_name() const;
// ===== Squid codes =====
-
- virtual SquidLogCode
- get_log_code() const
- {
- return SquidLogCode::EMPTY;
- }
- virtual SquidSubcode
- get_subcode() const
- {
- return SquidSubcode::EMPTY;
- }
- virtual SquidHitMissCode
- get_hit_miss_code() const
- {
- return SQUID_MISS_NONE;
- }
- virtual SquidHierarchyCode
- get_hier_code() const
- {
- return SquidHierarchyCode::NONE;
- }
+ SquidLogCode get_log_code() const;
+ SquidSubcode get_subcode() const;
+ SquidHitMissCode get_hit_miss_code() const;
+ SquidHierarchyCode get_hier_code() const;
// ===== Byte counters =====
-
- virtual int64_t
- get_client_request_body_bytes() const
- {
- return 0;
- }
- virtual int64_t
- get_client_response_hdr_bytes() const
- {
- return 0;
- }
- virtual int64_t
- get_client_response_body_bytes() const
- {
- return 0;
- }
- virtual int64_t
- get_server_request_body_bytes() const
- {
- return 0;
- }
- virtual int64_t
- get_server_response_body_bytes() const
- {
- return 0;
- }
- virtual int64_t
- get_cache_response_body_bytes() const
- {
- return 0;
- }
- virtual int64_t
- get_cache_response_hdr_bytes() const
- {
- return 0;
- }
+ int64_t get_client_request_body_bytes() const;
+ int64_t get_client_response_hdr_bytes() const;
+ int64_t get_client_response_body_bytes() const;
+ int64_t get_server_request_body_bytes() const;
+ int64_t get_server_response_body_bytes() const;
+ int64_t get_cache_response_body_bytes() const;
+ int64_t get_cache_response_hdr_bytes() const;
+
+ // ===== TLS handshake byte counters =====
+ uint64_t get_client_tls_handshake_bytes_rx() const;
+ uint64_t get_client_tls_handshake_bytes_tx() const;
+ size_t get_client_tls_early_data_len() const;
// ===== Transaction identifiers =====
-
- virtual int64_t
- get_sm_id() const
- {
- return 0;
- }
- virtual int64_t
- get_connection_id() const
- {
- return 0;
- }
- virtual int
- get_transaction_id() const
- {
- return 0;
- }
- virtual int
- get_transaction_priority_weight() const
- {
- return 0;
- }
- virtual int
- get_transaction_priority_dependence() const
- {
- return 0;
- }
+ int64_t get_sm_id() const;
+ int64_t get_connection_id() const;
+ int get_transaction_id() const;
+ int get_transaction_priority_weight() const;
+ int get_transaction_priority_dependence() const;
// ===== Plugin info =====
-
- virtual int64_t
- get_plugin_id() const
- {
- return 0;
- }
- virtual const char *
- get_plugin_tag() const
- {
- return nullptr;
- }
+ int64_t get_plugin_id() const;
+ const char *get_plugin_tag() const;
// ===== Protocol info =====
-
- virtual const char *
- get_client_protocol() const
- {
- return nullptr;
- }
- virtual const char *
- get_server_protocol() const
- {
- return nullptr;
- }
- virtual const char *
- get_client_sec_protocol() const
- {
- return nullptr;
- }
- virtual const char *
- get_client_cipher_suite() const
- {
- return nullptr;
- }
- virtual const char *
- get_client_curve() const
- {
- return nullptr;
- }
- virtual const char *
- get_client_security_group() const
- {
- return nullptr;
- }
- virtual int
- get_client_alpn_id() const
- {
- return -1;
- }
+ const char *get_client_protocol() const;
+ const char *get_server_protocol() const;
+ const char *get_client_sec_protocol() const;
+ const char *get_client_cipher_suite() const;
+ const char *get_client_curve() const;
+ const char *get_client_security_group() const;
+ int get_client_alpn_id() const;
// ===== SNI =====
-
- virtual const char *
- get_sni_server_name() const
- {
- return nullptr;
- }
+ const char *get_sni_server_name() const;
// ===== Connection flags =====
-
- virtual bool
- get_client_tcp_reused() const
- {
- return false;
- }
- virtual bool
- get_client_connection_is_ssl() const
- {
- return false;
- }
- virtual bool
- get_client_ssl_reused() const
- {
- return false;
- }
- virtual bool
- get_is_internal() const
- {
- return false;
- }
- virtual bool
- get_server_connection_is_ssl() const
- {
- return false;
- }
- virtual bool
- get_server_ssl_reused() const
- {
- return false;
- }
- virtual int
- get_server_connection_provided_cert() const
- {
- return 0;
- }
- virtual int
- get_client_provided_cert() const
- {
- return 0;
- }
+ bool get_client_tcp_reused() const;
+ bool get_client_connection_is_ssl() const;
+ bool get_client_ssl_reused() const;
+ int get_client_ssl_resumption_type() const;
+ bool get_is_internal() const;
+ bool get_server_connection_is_ssl() const;
+ bool get_server_ssl_reused() const;
+ int get_server_connection_provided_cert() const;
+ int get_client_provided_cert() const;
// ===== Server transaction count =====
-
- virtual int64_t
- get_server_transact_count() const
- {
- return 0;
- }
+ int64_t get_server_transact_count() const;
// ===== Finish status =====
-
- virtual int
- get_client_finish_status_code() const
- {
- return 0;
- }
- virtual int
- get_proxy_finish_status_code() const
- {
- return 0;
- }
+ int get_client_finish_status_code() const;
+ int get_proxy_finish_status_code() const;
// ===== Error codes =====
-
- virtual const char *
- get_client_rx_error_code() const
- {
- return "-";
- }
- virtual const char *
- get_client_tx_error_code() const
- {
- return "-";
- }
+ const char *get_client_rx_error_code() const;
+ const char *get_client_tx_error_code() const;
// ===== MPTCP =====
-
- virtual std::optional
- get_mptcp_state() const
- {
- return std::nullopt;
- }
+ std::optional get_mptcp_state() const;
// ===== Misc transaction state =====
-
- virtual in_port_t
- get_incoming_port() const
- {
- return 0;
- }
- virtual int
- get_orig_scheme() const
- {
- return -1;
- }
- virtual int64_t
- get_congestion_control_crat() const
- {
- return 0;
- }
+ in_port_t get_incoming_port() const;
+ int get_orig_scheme() const;
+ int64_t get_congestion_control_crat() const;
// ===== Cache state =====
-
- virtual int
- get_cache_write_code() const
- {
- return 0;
- }
- virtual int
- get_cache_transform_write_code() const
- {
- return 0;
- }
- virtual int
- get_cache_open_read_tries() const
- {
- return 0;
- }
- virtual int
- get_cache_open_write_tries() const
- {
- return 0;
- }
- virtual int
- get_max_cache_open_write_retries() const
- {
- return -1;
- }
+ int get_cache_write_code() const;
+ int get_cache_transform_write_code() const;
+ int get_cache_open_read_tries() const;
+ int get_cache_open_write_tries() const;
+ int get_max_cache_open_write_retries() const;
// ===== Retry attempts =====
-
- virtual int64_t
- get_simple_retry_attempts() const
- {
- return 0;
- }
- virtual int64_t
- get_unavailable_retry_attempts() const
- {
- return 0;
- }
- virtual int64_t
- get_retry_attempts_saved() const
- {
- return 0;
- }
+ int64_t get_simple_retry_attempts() const;
+ int64_t get_unavailable_retry_attempts() const;
+ int64_t get_retry_attempts_saved() const;
// ===== Status plugin entry name =====
-
- virtual std::string_view
- get_http_return_code_setter_name() const
- {
- return {};
- }
+ std::string_view get_http_return_code_setter_name() const;
// ===== Proxy Protocol =====
-
- virtual int
- get_pp_version() const
- {
- return 0;
- }
- virtual sockaddr const *
- get_pp_src_addr() const
- {
- return nullptr;
- }
- virtual sockaddr const *
- get_pp_dst_addr() const
- {
- return nullptr;
- }
- virtual std::string_view
- get_pp_authority() const
- {
- return {};
- }
- virtual std::string_view
- get_pp_tls_cipher() const
- {
- return {};
- }
- virtual std::string_view
- get_pp_tls_version() const
- {
- return {};
- }
- virtual std::string_view
- get_pp_tls_group() const
- {
- return {};
- }
+ int get_pp_version() const;
+ sockaddr const *get_pp_src_addr() const;
+ sockaddr const *get_pp_dst_addr() const;
+ std::string_view get_pp_authority() const;
+ std::string_view get_pp_tls_cipher() const;
+ std::string_view get_pp_tls_version() const;
+ std::string_view get_pp_tls_group() const;
// ===== Server response Transfer-Encoding =====
+ std::string_view get_server_response_transfer_encoding() const;
+
+ // ===== Fallback fields for non-HttpSM logging =====
+ std::string_view get_method() const;
+ std::string_view get_scheme() const;
+ std::string_view get_client_protocol_str() const;
- virtual std::string_view
- get_server_response_transfer_encoding() const
- {
- return {};
- }
-
- // ===== Fallback fields for pre-transaction logging =====
-
- virtual std::string_view
- get_method() const
- {
- return {};
- }
- virtual std::string_view
- get_scheme() const
- {
- return {};
- }
- virtual std::string_view
- get_client_protocol_str() const
- {
- return {};
- }
-
- // noncopyable
TransactionLogData(const TransactionLogData &) = delete;
TransactionLogData &operator=(const TransactionLogData &) = delete;
-protected:
- TransactionLogData() = default;
+private:
+ HttpSM *m_http_sm = nullptr;
+ NonHttpSmLogData const *m_non_http_sm_data = nullptr;
+
+ // Cached values for fields that require computation or string formatting.
+ mutable char m_client_rx_error_code[10] = {'-', '\0'};
+ mutable char m_client_tx_error_code[10] = {'-', '\0'};
+ mutable bool m_error_codes_formatted = false;
+
+ // Cached URL string pointers (computed on first access).
+ mutable const char *m_client_req_url_str = nullptr;
+ mutable int m_client_req_url_len = 0;
+ mutable const char *m_client_req_url_path_str = nullptr;
+ mutable int m_client_req_url_path_len = 0;
+ mutable bool m_url_cached = false;
+
+ // Cached content-type pointers (computed on first access).
+ mutable char *m_proxy_resp_content_type_str = nullptr;
+ mutable int m_proxy_resp_content_type_len = 0;
+ mutable char *m_proxy_resp_reason_phrase_str = nullptr;
+ mutable int m_proxy_resp_reason_phrase_len = 0;
+ mutable bool m_content_type_cached = false;
+
+ void cache_url_strings() const;
+ void cache_content_type() const;
+ void format_error_codes() const;
};
diff --git a/include/records/YAMLConfigReloadTaskEncoder.h b/include/records/YAMLConfigReloadTaskEncoder.h
index 5aee5a7bca4..a86b863ba9d 100644
--- a/include/records/YAMLConfigReloadTaskEncoder.h
+++ b/include/records/YAMLConfigReloadTaskEncoder.h
@@ -54,8 +54,11 @@ template <> struct convert {
node["logs"] = YAML::Node(YAML::NodeType::Sequence);
// if no logs, it will be empty sequence.
- for (const auto &log : info.logs) {
- node["logs"].push_back(log);
+ for (const auto &entry : info.logs) {
+ YAML::Node log_node;
+ log_node["level"] = static_cast(entry.level);
+ log_node["text"] = entry.text;
+ node["logs"].push_back(log_node);
}
node["sub_tasks"] = YAML::Node(YAML::NodeType::Sequence);
diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in
index f458884779e..fc2403892bc 100644
--- a/include/ts/apidefs.h.in
+++ b/include/ts/apidefs.h.in
@@ -825,6 +825,7 @@ enum TSOverridableConfigKey {
TS_CONFIG_HTTP_TRANSACTION_ACTIVE_TIMEOUT_OUT,
TS_CONFIG_HTTP_CONNECT_ATTEMPTS_MAX_RETRIES,
TS_CONFIG_HTTP_CONNECT_ATTEMPTS_MAX_RETRIES_DOWN_SERVER,
+ TS_CONFIG_HTTP_CONNECT_ATTEMPTS_MAX_RETRIES_SUSPECT_SERVER,
TS_CONFIG_HTTP_CONNECT_ATTEMPTS_RR_RETRIES,
TS_CONFIG_HTTP_CONNECT_ATTEMPTS_TIMEOUT,
TS_CONFIG_HTTP_DOWN_SERVER_CACHE_TIME,
@@ -916,6 +917,7 @@ enum TSOverridableConfigKey {
TS_CONFIG_HTTP_NEGATIVE_REVALIDATING_LIST,
TS_CONFIG_HTTP_CACHE_POST_METHOD,
TS_CONFIG_HTTP_CACHE_TARGETED_CACHE_CONTROL_HEADERS,
+ TS_CONFIG_SSL_CLIENT_CA_CERT_PATH,
TS_CONFIG_LAST_ENTRY,
};
@@ -1649,10 +1651,10 @@ struct TSResponseAction {
};
enum TSLogType {
- TS_LOG_TYPE_INT,
+ TS_LOG_TYPE_INT = 1, ///< LogField::Type::sINT
// DINT is omitted from the public API for now, until we decide whether we keep the type
- TS_LOG_TYPE_STRING = 2,
- TS_LOG_TYPE_ADDR = 3,
+ TS_LOG_TYPE_STRING = 3, ///< LogField::Type::STRING
+ TS_LOG_TYPE_ADDR = 4, ///< LogField::Type::IP
};
/* --------------------------------------------------------------------------
diff --git a/include/ts/ts.h b/include/ts/ts.h
index 9b62d64fe8c..5631bee0f66 100644
--- a/include/ts/ts.h
+++ b/include/ts/ts.h
@@ -1168,6 +1168,26 @@ TSReturnCode TSMutexLockTry(TSMutex mutexp);
void TSMutexUnlock(TSMutex mutexp);
+/** Scoped lock guard for a @c TSMutex.
+
+ Locks @a mutexp on construction and unlocks it when the guard leaves scope.
+ */
+class [[nodiscard]] TSMutexLockGuard
+{
+public:
+ explicit TSMutexLockGuard(TSMutex mutexp) : m_mutex(mutexp) { TSMutexLock(m_mutex); }
+
+ TSMutexLockGuard(const TSMutexLockGuard &) = delete;
+ TSMutexLockGuard &operator=(const TSMutexLockGuard &) = delete;
+ TSMutexLockGuard(TSMutexLockGuard &&) = delete;
+ TSMutexLockGuard &operator=(TSMutexLockGuard &&) = delete;
+
+ ~TSMutexLockGuard() { TSMutexUnlock(m_mutex); }
+
+private:
+ TSMutex m_mutex = nullptr;
+};
+
/* --------------------------------------------------------------------------
cachekey */
/**
@@ -2768,6 +2788,25 @@ TSReturnCode TSHttpTxnCachedRespModifiableGet(TSHttpTxn txnp, TSMBuffer *bufp, T
TSReturnCode TSHttpTxnCacheLookupStatusSet(TSHttpTxn txnp, int cachelookup);
TSReturnCode TSHttpTxnCacheLookupUrlGet(TSHttpTxn txnp, TSMBuffer bufp, TSMLoc obj);
TSReturnCode TSHttpTxnCacheLookupUrlSet(TSHttpTxn txnp, TSMBuffer bufp, TSMLoc obj);
+
+/**
+ Gets the effective cache key digest (cryptographic hash) that was
+ used for cache lookup or storage on this transaction. The digest
+ is returned as raw bytes — 16 bytes for MD5 (default) or 32 bytes
+ for SHA-256 (FIPS mode). A buffer of at least 32 bytes is
+ recommended to accommodate either configuration.
+
+ @param[in] txnp the transaction.
+ @param[out] buffer caller-provided buffer to receive the raw hash
+ bytes. If @c nullptr, only @a length is set (size query).
+ @param[in,out] length capacity of @a buffer in bytes on input; actual
+ digest size in bytes on output.
+
+ @return @c TS_SUCCESS if a cache key was computed for this
+ transaction, @c TS_ERROR if no cache lookup was performed or if
+ @a buffer is non-null and too small.
+ */
+TSReturnCode TSHttpTxnCacheKeyDigestGet(TSHttpTxn txnp, char *buffer, int *length);
TSReturnCode TSHttpTxnPrivateSessionSet(TSHttpTxn txnp, int private_session);
const char *TSHttpTxnCacheDiskPathGet(TSHttpTxn txnp, int *length);
int TSHttpTxnBackgroundFillStarted(TSHttpTxn txnp);
diff --git a/include/tscore/DiagsTypes.h b/include/tscore/DiagsTypes.h
index f770e871c4f..1b3713160b5 100644
--- a/include/tscore/DiagsTypes.h
+++ b/include/tscore/DiagsTypes.h
@@ -32,6 +32,7 @@
#pragma once
#include
+#include
#include
#include
#include "tsutil/DbgCtl.h"
@@ -242,9 +243,9 @@ class Diags : public DebugInterface
IpAddr debug_client_ip;
private:
- const std::string prefix_str;
- mutable ink_mutex tag_table_lock; // prevents reconfig/read races
- Regex *activated_tags[2]; // 1 table for debug, 1 for action
+ const std::string prefix_str;
+ mutable ink_mutex tag_table_lock; // prevents reconfig/read races
+ std::shared_ptr activated_tags[2]; // 1 table for debug, 1 for action
// These are the default logfile permissions
int diags_logfile_perm = -1;
diff --git a/include/tscore/HashFNV.h b/include/tscore/HashFNV.h
index 0ea09bbe08a..57a7c4b0bb1 100644
--- a/include/tscore/HashFNV.h
+++ b/include/tscore/HashFNV.h
@@ -45,7 +45,8 @@ struct ATSHash32FNV1a : ATSHash32 {
void clear() override;
private:
- uint32_t hval;
+ static constexpr uint32_t fnv_init = 0x811c9dc5u;
+ uint32_t hval{fnv_init};
};
template
@@ -76,7 +77,8 @@ struct ATSHash64FNV1a : ATSHash64 {
void clear() override;
private:
- uint64_t hval;
+ static constexpr uint64_t fnv_init = 0xcbf29ce484222325ull;
+ uint64_t hval{fnv_init};
};
template
diff --git a/include/tscore/TsBuffer.h b/include/tscore/TsBuffer.h
deleted file mode 100644
index 857437eb511..00000000000
--- a/include/tscore/TsBuffer.h
+++ /dev/null
@@ -1,508 +0,0 @@
-/** @file
- Definitions for a buffer type, to carry a reference to a chunk of memory.
-
- @section license License
-
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you 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.
- */
-
-#pragma once
-
-#if defined _MSC_VER
-#include
-#else
-#include
-#endif
-
-// For memcmp()
-#include
-#include
-
-/// Apache Traffic Server commons.
-namespace ts
-{
-struct ConstBuffer;
-/** A chunk of writable memory.
- A convenience class because we pass this kind of pair frequently.
-
- @note The default construct leaves the object
- uninitialized. This is for performance reasons. To construct an
- empty @c Buffer use @c Buffer(0).
- */
-struct Buffer {
- using self = Buffer; ///< Self reference type.
- using pseudo_bool = bool (self::*)() const;
-
- char *_ptr = nullptr; ///< Pointer to base of memory chunk.
- size_t _size = 0; ///< Size of memory chunk.
-
- /// Default constructor (empty buffer).
- Buffer();
-
- /** Construct from pointer and size.
- @note Due to ambiguity issues do not call this with
- two arguments if the first argument is 0.
- */
- Buffer(char *ptr, ///< Pointer to buffer.
- size_t n ///< Size of buffer.
- );
- /** Construct from two pointers.
- @note This presumes a half open range, (start, end]
- */
- Buffer(char *start, ///< First valid character.
- char *end ///< First invalid character.
- );
-
- /** Equality.
- @return @c true if @a that refers to the same memory as @a this,
- @c false otherwise.
- */
- bool operator==(self const &that) const;
- /** Inequality.
- @return @c true if @a that does not refer to the same memory as @a this,
- @c false otherwise.
- */
- bool operator!=(self const &that) const;
- /** Equality for a constant buffer.
- @return @c true if @a that refers to the same memory as @a this.
- @c false otherwise.
- */
- bool operator==(ConstBuffer const &that) const;
- /** Inequality.
- @return @c true if @a that does not refer to the same memory as @a this,
- @c false otherwise.
- */
- bool operator!=(ConstBuffer const &that) const;
-
- /// @return The first character in the buffer.
- char operator*() const;
- /** Discard the first character in the buffer.
- @return @a this object.
- */
- self &operator++();
-
- /// Check for empty buffer.
- /// @return @c true if the buffer has a zero pointer @b or size.
- bool operator!() const;
- /// Check for non-empty buffer.
- /// @return @c true if the buffer has a non-zero pointer @b and size.
- operator pseudo_bool() const;
-
- /// @name Accessors.
- //@{
- /// Get the data in the buffer.
- char *data() const;
- /// Get the size of the buffer.
- size_t size() const;
- //@}
-
- /// Set the chunk.
- /// Any previous values are discarded.
- /// @return @c this object.
- self &set(char *ptr, ///< Buffer address.
- size_t n = 0 ///< Buffer size.
- );
- /// Reset to empty.
- self &reset();
-};
-
-/** A chunk of read only memory.
- A convenience class because we pass this kind of pair frequently.
- */
-struct ConstBuffer {
- using self = ConstBuffer; ///< Self reference type.
- using pseudo_bool = bool (self::*)() const;
-
- char const *_ptr = nullptr; ///< Pointer to base of memory chunk.
- size_t _size = 0; ///< Size of memory chunk.
-
- /// Default constructor (empty buffer).
- ConstBuffer();
-
- /** Construct from pointer and size.
- */
- ConstBuffer(char const *ptr, ///< Pointer to buffer.
- size_t n ///< Size of buffer.
- );
- /** Construct from two pointers.
- @note This presumes a half open range (start, end]
- @note Due to ambiguity issues do not invoke this with
- @a start == 0.
- */
- ConstBuffer(char const *start, ///< First valid character.
- char const *end ///< First invalid character.
- );
- /// Construct from writable buffer.
- ConstBuffer(Buffer const &buffer ///< Buffer to copy.
- );
-
- /** Equality.
- @return @c true if @a that refers to the same memory as @a this,
- @c false otherwise.
- */
- bool operator==(self const &that) const;
- /** Equality.
- @return @c true if @a that refers to the same memory as @a this,
- @c false otherwise.
- */
- bool operator==(Buffer const &that) const;
- /** Inequality.
- @return @c true if @a that does not refer to the same memory as @a this,
- @c false otherwise.
- */
- bool operator!=(self const &that) const;
- /** Inequality.
- @return @c true if @a that does not refer to the same memory as @a this,
- @c false otherwise.
- */
- bool operator!=(Buffer const &that) const;
- /// Assign from non-const Buffer.
- self &operator=(Buffer const &that ///< Source buffer.
- );
-
- /// @return The first character in the buffer.
- char operator*() const;
- /** Discard the first character in the buffer.
- @return @a this object.
- */
- self &operator++();
- /** Discard the first @a n characters.
- @return @a this object.
- */
- self &operator+=(size_t n);
-
- /// Check for empty buffer.
- /// @return @c true if the buffer has a zero pointer @b or size.
- bool operator!() const;
- /// Check for non-empty buffer.
- /// @return @c true if the buffer has a non-zero pointer @b and size.
- operator pseudo_bool() const;
-
- operator std::string_view() const { return {_ptr, _size}; }
-
- /// @name Accessors.
- //@{
- /// Get the data in the buffer.
- char const *data() const;
- /// Get the size of the buffer.
- size_t size() const;
- /// Access a character (no bounds check).
- char operator[](int n) const;
- //@}
- /// @return @c true if @a p points at a character in @a this.
- bool contains(char const *p) const;
-
- /// Set the chunk.
- /// Any previous values are discarded.
- /// @return @c this object.
- self &set(char const *ptr, ///< Buffer address.
- size_t n = 0 ///< Buffer size.
- );
- /** Set from 2 pointers.
- @note This presumes a half open range (start, end]
- */
- self &set(char const *start, ///< First valid character.
- char const *end ///< First invalid character.
- );
- /// Reset to empty.
- self &reset();
-
- /** Find a character.
- @return A pointer to the first occurrence of @a c in @a this
- or @c nullptr if @a c is not found.
- */
- char const *find(char c) const;
-
- /** Split the buffer on the character at @a p.
-
- The buffer is split in to two parts and the character at @a p
- is discarded. @a this retains all data @b after @a p. The
- initial part of the buffer is returned. Neither buffer will
- contain the character at @a p.
-
- This is convenient when tokenizing and @a p points at the token
- separator.
-
- @note If @a *p is not in the buffer then @a this is not changed
- and an empty buffer is returned. This means the caller can
- simply pass the result of @c find and check for an empty
- buffer returned to detect no more separators.
-
- @return A buffer containing data up to but not including @a p.
- */
- self splitOn(char const *p);
-
- /** Split the buffer on the character @a c.
-
- The buffer is split in to two parts and the occurrence of @a c
- is discarded. @a this retains all data @b after @a c. The
- initial part of the buffer is returned. Neither buffer will
- contain the first occurrence of @a c.
-
- This is convenient when tokenizing and @a c is the token
- separator.
-
- @note If @a c is not found then @a this is not changed and an
- empty buffer is returned.
-
- @return A buffer containing data up to but not including @a p.
- */
- self splitOn(char c);
- /** Get a trailing segment of the buffer.
-
- @return A buffer that contains all data after @a p.
- */
- self after(char const *p) const;
- /** Get a trailing segment of the buffer.
-
- @return A buffer that contains all data after the first
- occurrence of @a c.
- */
- self after(char c) const;
- /** Remove trailing segment.
-
- Data at @a p and beyond is removed from the buffer.
- If @a p is not in the buffer, no change is made.
-
- @return @a this.
- */
- self &clip(char const *p);
-};
-
-// ----------------------------------------------------------
-// Inline implementations.
-
-inline Buffer::Buffer() {}
-inline Buffer::Buffer(char *ptr, size_t n) : _ptr(ptr), _size(n) {}
-inline Buffer &
-Buffer::set(char *ptr, size_t n)
-{
- _ptr = ptr;
- _size = n;
- return *this;
-}
-inline Buffer::Buffer(char *start, char *end) : _ptr(start), _size(end - start) {}
-inline Buffer &
-Buffer::reset()
-{
- _ptr = nullptr;
- _size = 0;
- return *this;
-}
-inline bool
-Buffer::operator!=(self const &that) const
-{
- return !(*this == that);
-}
-inline bool
-Buffer::operator!=(ConstBuffer const &that) const
-{
- return !(*this == that);
-}
-inline bool
-Buffer::operator==(self const &that) const
-{
- return _size == that._size && _ptr == that._ptr;
-}
-inline bool
-Buffer::operator==(ConstBuffer const &that) const
-{
- return _size == that._size && _ptr == that._ptr;
-}
-inline bool
-Buffer::operator!() const
-{
- return !(_ptr && _size);
-}
-inline Buffer::operator pseudo_bool() const
-{
- return _ptr && _size ? &self::operator! : nullptr;
-}
-inline char
-Buffer::operator*() const
-{
- return *_ptr;
-}
-inline Buffer &
-Buffer::operator++()
-{
- ++_ptr;
- --_size;
- return *this;
-}
-inline char *
-Buffer::data() const
-{
- return _ptr;
-}
-inline size_t
-Buffer::size() const
-{
- return _size;
-}
-
-inline ConstBuffer::ConstBuffer() {}
-inline ConstBuffer::ConstBuffer(char const *ptr, size_t n) : _ptr(ptr), _size(n) {}
-inline ConstBuffer::ConstBuffer(char const *start, char const *end) : _ptr(start), _size(end - start) {}
-inline ConstBuffer::ConstBuffer(Buffer const &that) : _ptr(that._ptr), _size(that._size) {}
-inline ConstBuffer &
-ConstBuffer::set(char const *ptr, size_t n)
-{
- _ptr = ptr;
- _size = n;
- return *this;
-}
-
-inline ConstBuffer &
-ConstBuffer::set(char const *start, char const *end)
-{
- _ptr = start;
- _size = end - start;
- return *this;
-}
-
-inline ConstBuffer &
-ConstBuffer::reset()
-{
- _ptr = nullptr;
- _size = 0;
- return *this;
-}
-inline bool
-ConstBuffer::operator!=(self const &that) const
-{
- return !(*this == that);
-}
-inline bool
-ConstBuffer::operator!=(Buffer const &that) const
-{
- return !(*this == that);
-}
-inline bool
-ConstBuffer::operator==(self const &that) const
-{
- return _size == that._size && 0 == memcmp(_ptr, that._ptr, _size);
-}
-inline ConstBuffer &
-ConstBuffer::operator=(Buffer const &that)
-{
- _ptr = that._ptr;
- _size = that._size;
- return *this;
-}
-inline bool
-ConstBuffer::operator==(Buffer const &that) const
-{
- return _size == that._size && 0 == memcmp(_ptr, that._ptr, _size);
-}
-inline bool
-ConstBuffer::operator!() const
-{
- return !(_ptr && _size);
-}
-inline ConstBuffer::operator pseudo_bool() const
-{
- return _ptr && _size ? &self::operator! : nullptr;
-}
-inline char
-ConstBuffer::operator*() const
-{
- return *_ptr;
-}
-inline ConstBuffer &
-ConstBuffer::operator++()
-{
- ++_ptr;
- --_size;
- return *this;
-}
-inline ConstBuffer &
-ConstBuffer::operator+=(size_t n)
-{
- _ptr += n;
- _size -= n;
- return *this;
-}
-inline char const *
-ConstBuffer::data() const
-{
- return _ptr;
-}
-inline char
-ConstBuffer::operator[](int n) const
-{
- return _ptr[n];
-}
-inline size_t
-ConstBuffer::size() const
-{
- return _size;
-}
-inline bool
-ConstBuffer::contains(char const *p) const
-{
- return _ptr <= p && p < _ptr + _size;
-}
-
-inline ConstBuffer
-ConstBuffer::splitOn(char const *p)
-{
- self zret; // default to empty return.
- if (this->contains(p)) {
- size_t n = p - _ptr;
- zret.set(_ptr, n);
- _ptr = p + 1;
- _size -= n + 1;
- }
- return zret;
-}
-
-inline char const *
-ConstBuffer::find(char c) const
-{
- return static_cast(memchr(_ptr, c, _size));
-}
-
-inline ConstBuffer
-ConstBuffer::splitOn(char c)
-{
- return this->splitOn(this->find(c));
-}
-
-inline ConstBuffer
-ConstBuffer::after(char const *p) const
-{
- return this->contains(p) ? self(p + 1, (_size - (p - _ptr)) - 1) : self();
-}
-inline ConstBuffer
-ConstBuffer::after(char c) const
-{
- return this->after(this->find(c));
-}
-inline ConstBuffer &
-ConstBuffer::clip(char const *p)
-{
- if (this->contains(p)) {
- _size = p - _ptr;
- }
- return *this;
-}
-
-} // namespace ts
-
-using TsBuffer = ts::Buffer;
-using TsConstBuffer = ts::ConstBuffer;
diff --git a/include/tscore/ink_config.h.cmake.in b/include/tscore/ink_config.h.cmake.in
index bf012cefecd..73c8b860fb9 100644
--- a/include/tscore/ink_config.h.cmake.in
+++ b/include/tscore/ink_config.h.cmake.in
@@ -186,6 +186,8 @@ const int DEFAULT_STACKSIZE = @DEFAULT_STACK_SIZE@;
#cmakedefine01 HAVE_SSL_ERROR_DESCRIPTION
#cmakedefine01 HAVE_OSSL_PARAM_CONSTRUCT_END
#cmakedefine01 TS_USE_TLS_SET_CIPHERSUITES
+#cmakedefine01 HAVE_SSL_CTX_ADD_CERT_COMPRESSION_ALG
+#cmakedefine01 HAVE_SSL_CTX_SET1_CERT_COMP_PREFERENCE
#define TS_BUILD_CANONICAL_HOST "@CMAKE_HOST@"
diff --git a/include/tscore/ink_inet.h b/include/tscore/ink_inet.h
index 18319b13535..d0cc2433644 100644
--- a/include/tscore/ink_inet.h
+++ b/include/tscore/ink_inet.h
@@ -34,6 +34,7 @@
#include "tscore/ink_memory.h"
#include "tscore/ink_apidefs.h"
+#include "tscore/ink_string.h"
#include "swoc/bwf_fwd.h"
#if !TS_HAS_IN6_IS_ADDR_UNSPECIFIED
@@ -1610,16 +1611,21 @@ struct UnAddr {
UnAddr() { _path[0] = 0; }
- UnAddr(self const &addr) { strncpy(_path, addr._path, TS_UNIX_SIZE); }
- explicit UnAddr(const char *path) { strncpy(_path, path, TS_UNIX_SIZE - 1); }
- explicit UnAddr(const std::string &path) { strncpy(_path, path.c_str(), TS_UNIX_SIZE); }
+ UnAddr(self const &addr) { ink_strlcpy(_path, addr._path, TS_UNIX_SIZE); }
+ explicit UnAddr(const char *path) { ink_strlcpy(_path, path, TS_UNIX_SIZE); }
+ explicit UnAddr(const std::string &path) { ink_strlcpy(_path, path.c_str(), TS_UNIX_SIZE); }
explicit UnAddr(sockaddr const *addr) { this->assign(addr); }
explicit UnAddr(sockaddr_un const *addr) { this->assign(ats_ip_sa_cast(addr)); }
/// Construct from @c IpEndpoint.
explicit UnAddr(IpEndpoint const &addr) { this->assign(&addr.sa); }
/// Construct from @c IpEndpoint.
- explicit UnAddr(IpEndpoint const *addr) { this->assign(&addr->sa); }
+ explicit UnAddr(IpEndpoint const *addr) : UnAddr()
+ {
+ if (addr) {
+ this->assign(&addr->sa);
+ }
+ }
/// Assign sockaddr storage.
self &assign(sockaddr const *addr);
@@ -1639,7 +1645,7 @@ struct UnAddr {
operator=(self const &addr)
{
if (this != &addr) {
- strncpy(_path, addr._path, TS_UNIX_SIZE);
+ ink_strlcpy(_path, addr._path, TS_UNIX_SIZE);
}
return *this;
}
@@ -1651,7 +1657,11 @@ inline UnAddr &
UnAddr::assign(sockaddr const *addr)
{
if (addr) {
- strncpy(_path, ats_unix_cast(addr)->sun_path, TS_UNIX_SIZE);
+ // A kernel sockaddr_un may carry a full, unterminated sun_path.
+ strncpy(_path, ats_unix_cast(addr)->sun_path, TS_UNIX_SIZE - 1);
+ _path[TS_UNIX_SIZE - 1] = '\0';
+ } else {
+ _path[0] = '\0';
}
return *this;
}
diff --git a/include/tscore/ink_sys_control.h b/include/tscore/ink_sys_control.h
index 81e24f35ebc..c770376c324 100644
--- a/include/tscore/ink_sys_control.h
+++ b/include/tscore/ink_sys_control.h
@@ -24,8 +24,21 @@
#pragma once
#include
+#include
rlim_t ink_get_fds_limit();
void ink_set_fds_limit(rlim_t);
rlim_t ink_max_out_rlimit(int which);
rlim_t ink_get_max_files();
+
+/** Get the current resident set size (RSS) of this process, in bytes.
+ *
+ * Unlike getrusage(2)'s ru_maxrss, this reports the *current* RSS rather than
+ * the peak, so it is suitable for a live gauge and can both rise and fall.
+ *
+ * Implemented via /proc/self/statm on Linux, task_info() on macOS, and
+ * sysctl(KERN_PROC_PID) on FreeBSD.
+ *
+ * @return current RSS in bytes, or 0 if it could not be determined.
+ */
+uint64_t ink_get_current_rss();
diff --git a/include/tsutil/Bravo.h b/include/tsutil/Bravo.h
index 4660f69aca6..1dc1043d680 100644
--- a/include/tsutil/Bravo.h
+++ b/include/tsutil/Bravo.h
@@ -39,6 +39,7 @@
#include "tsutil/DenseThreadId.h"
#include "tsutil/Assert.h"
+#include "tsutil/ts_thread_safety.h"
#include
#include
@@ -68,129 +69,54 @@ using Token = size_t;
/**
ts::bravo::shared_lock
+
+ Reader guard for shared_mutex_impl, carrying the BRAVO Token. A rigid scoped
+ capability: it acquires the shared lock in its constructor and releases it in
+ its destructor, with no copy, move, defer or release. That rigidity is what
+ lets the analysis track it -- the deferred and movable forms of
+ std::shared_lock let the held state escape a single scope and cannot be
+ modelled -- and BRAVO readers need only this scoped form.
*/
-template class shared_lock
+template class TS_SCOPED_CAPABILITY shared_lock
{
public:
using mutex_type = Mutex;
- shared_lock() noexcept = default;
- shared_lock(Mutex &m) : _mutex(&m) { lock(); }
- shared_lock(Mutex &m, std::try_to_lock_t) : _mutex(&m) { try_lock(); }
- shared_lock(Mutex &m, std::defer_lock_t) noexcept : _mutex(&m) {}
-
- ~shared_lock()
- {
- if (_owns) {
- _mutex->unlock_shared(_token);
- }
- };
+ explicit shared_lock(Mutex &m) TS_ACQUIRE_SHARED(m) : _mutex(&m) { _mutex->lock_shared(_token); }
+ ~shared_lock() TS_RELEASE() { _mutex->unlock_shared(_token); }
////
- // Not Copyable
+ // Neither copyable nor movable: the held shared lock must not escape this scope.
//
shared_lock(shared_lock const &) = delete;
shared_lock &operator=(shared_lock const &) = delete;
-
- ////
- // Moveable
- //
- shared_lock(shared_lock &&s) : _mutex(s._mutex), _token(s._token), _owns(s._owns)
- {
- s._mutex = nullptr;
- s._token = 0;
- s._owns = false;
- };
-
- shared_lock &
- operator=(shared_lock &&s)
- {
- if (_owns) {
- _mutex->unlock_shared(_token);
- }
- _mutex = s._mutex;
- _token = s._token;
- _owns = s._owns;
-
- s._mutex = nullptr;
- s._token = 0;
- s._owns = false;
- };
-
- ////
- // Shared locking
- //
- void
- lock()
- {
- _mutex->lock_shared(_token);
- _owns = true;
- }
-
- bool
- try_lock()
- {
- _owns = _mutex->try_lock_shared(_token);
- return _owns;
- }
-
- // not implemented yet
- bool try_lock_for() = delete;
- bool try_lock_until() = delete;
-
- void
- unlock()
- {
- _mutex->unlock_shared(_token);
- _owns = false;
- }
-
- ////
- // Modifiers
- //
- void
- swap(shared_lock &s)
- {
- std::swap(_mutex, s._mutex);
- std::swap(_token, s._token);
- std::swap(_owns, s._owns);
- }
-
- mutex_type *
- release()
- {
- mutex_type *m = _mutex;
- _mutex = nullptr;
- _token = 0;
- _owns = false;
- return m;
- }
+ shared_lock(shared_lock &&) = delete;
+ shared_lock &operator=(shared_lock &&) = delete;
////
// Observers
//
mutex_type *
- mutex()
+ mutex() const
{
return _mutex;
}
Token
- token()
+ token() const
{
return _token;
}
bool
- owns_lock()
+ owns_lock() const
{
- return _owns;
+ return _mutex != nullptr;
}
private:
mutex_type *_mutex = nullptr;
Token _token = 0;
- bool _owns = false;
};
/**
@@ -201,7 +127,8 @@ template class shared_lock
Set the SLOT_SIZE larger than DenseThreadId::num_possible_values to go fast-path.
*/
-template class shared_mutex_impl
+template
+class TS_CAPABILITY("shared_mutex") shared_mutex_impl
{
public:
shared_mutex_impl() = default;
@@ -219,15 +146,22 @@ template = DenseThreadId::num_possible_values());
+ // Clear up front so a slow-path acquisition never leaves a stale fast-path slot for unlock_shared().
+ token = 0;
+
// Fast path
if (_mutex.read_bias.load(std::memory_order_acquire)) {
size_t index = DenseThreadId::self() % SLOT_SIZE;
@@ -277,10 +214,13 @@ template = DenseThreadId::num_possible_values());
+ // Clear up front so a slow-path acquisition never leaves a stale fast-path slot for unlock_shared().
+ token = 0;
+
// Fast path
if (_mutex.read_bias.load(std::memory_order_acquire)) {
size_t index = DenseThreadId::self() % SLOT_SIZE;
@@ -313,7 +253,7 @@ template ();
release_assert(_blobs[0]);
@@ -325,7 +326,7 @@ class Metrics
~Storage() {}
IdType create(const std::string_view name, const MetricType type = MetricType::COUNTER);
- void addBlob();
+ void addBlob() TS_REQUIRES(_mutex);
IdType lookup(const std::string_view name) const;
AtomicType *lookup(const std::string_view name, IdType *out_id, MetricType *out_type = nullptr) const;
AtomicType *lookup(Metrics::IdType id, std::string_view *out_name = nullptr, MetricType *out_type = nullptr) const;
@@ -337,7 +338,7 @@ class Metrics
std::pair
current() const
{
- std::lock_guard lock(_mutex);
+ ts::lock_guard lock(_mutex);
return {_cur_blob, _cur_off};
}
@@ -346,6 +347,7 @@ class Metrics
{
auto [blob, entry] = _splitID(id);
+ ts::lock_guard lock(_mutex);
return (id >= 0 && ((blob < _cur_blob && entry < MAX_SIZE) || (blob == _cur_blob && entry <= _cur_off)));
}
};
diff --git a/include/tsutil/Regex.h b/include/tsutil/Regex.h
index cc5260c24f5..3295712bdad 100644
--- a/include/tsutil/Regex.h
+++ b/include/tsutil/Regex.h
@@ -23,10 +23,11 @@
#pragma once
+#include
+#include
#include
#include
#include
-#include
/// @brief Match flags for regular expression evaluation.
///
diff --git a/include/tsutil/StringCompare.h b/include/tsutil/StringCompare.h
new file mode 100644
index 00000000000..fa054ccf4e2
--- /dev/null
+++ b/include/tsutil/StringCompare.h
@@ -0,0 +1,49 @@
+/** @file
+
+ Helper for std::string_view comparison
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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.
+ */
+
+#pragma once
+
+#include
+#include
+
+namespace ts
+{
+/**
+ Returns true iff @a lhs and @a rhs compare equal, ignoring case.
+
+ Prefer this over libswoc's @c strcasecmp(std::string_view, std::string_view) when you only need
+ an equality check: this short-circuits on length mismatch, whereas the libswoc version must keep
+ comparing bytes to produce a correct ordering result even when the lengths differ.
+
+ For case-sensitive comparison, use @c std::string_view::operator==.
+ */
+inline bool
+iequals(std::string_view lhs, std::string_view rhs) noexcept
+{
+ if (lhs.size() != rhs.size()) {
+ return false;
+ }
+
+ return ::strncasecmp(lhs.data(), rhs.data(), lhs.size()) == 0;
+}
+} // namespace ts
diff --git a/include/tsutil/TsMutex.h b/include/tsutil/TsMutex.h
new file mode 100644
index 00000000000..6bc193dc4ea
--- /dev/null
+++ b/include/tsutil/TsMutex.h
@@ -0,0 +1,100 @@
+/** @file
+
+ A std::mutex annotated for Clang Thread Safety Analysis, with a matching
+ scoped lock guard.
+
+ These are the plain-mutex counterparts to ts::shared_mutex and its
+ reader/writer guards (TsSharedMutex.h): use ts::mutex with ts::lock_guard
+ wherever you would otherwise use std::mutex with std::lock_guard, but want the
+ data it protects checked by -Wthread-safety. The runtime behavior is exactly
+ that of std::mutex; the annotations are compile-time only (see
+ tsutil/ts_thread_safety.h).
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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.
+ */
+
+#pragma once
+
+#include
+
+#include "tsutil/ts_thread_safety.h"
+
+namespace ts
+{
+// A std::mutex marked as a Clang thread-safety capability, so data guarded by it
+// can be checked by -Wthread-safety. Same interface and runtime behavior as
+// std::mutex.
+//
+class TS_CAPABILITY("mutex") mutex
+{
+public:
+ mutex() = default;
+ mutex(mutex const &) = delete;
+ mutex &operator=(mutex const &) = delete;
+
+ // The lock/unlock bodies are the trusted implementation of this capability:
+ // exempt them from analysis so only the capability contract on each signature
+ // is checked. The actual data-race checking happens at the call sites.
+ void
+ lock() TS_ACQUIRE() TS_NO_THREAD_SAFETY_ANALYSIS
+ {
+ _m.lock();
+ }
+ bool
+ try_lock() TS_TRY_ACQUIRE(true) TS_NO_THREAD_SAFETY_ANALYSIS
+ {
+ return _m.try_lock();
+ }
+ void
+ unlock() TS_RELEASE() TS_NO_THREAD_SAFETY_ANALYSIS
+ {
+ _m.unlock();
+ }
+
+ using native_handle_type = std::mutex::native_handle_type;
+
+ native_handle_type
+ native_handle()
+ {
+ return _m.native_handle();
+ }
+
+private:
+ std::mutex _m;
+};
+
+// RAII guard for ts::mutex that carries Clang thread-safety capability state.
+// Prefer over std::lock_guard / std::unique_lock in code annotated for
+// -Wthread-safety (see tsutil/ts_thread_safety.h for why the std wrappers are
+// not tracked).
+//
+class TS_SCOPED_CAPABILITY lock_guard
+{
+public:
+ explicit lock_guard(mutex &m) TS_ACQUIRE(m) : _m(m) { _m.lock(); }
+ ~lock_guard() TS_RELEASE() { _m.unlock(); }
+
+ lock_guard(lock_guard const &) = delete;
+ lock_guard &operator=(lock_guard const &) = delete;
+
+private:
+ mutex &_m;
+};
+
+} // end namespace ts
diff --git a/include/tsutil/TsSharedMutex.h b/include/tsutil/TsSharedMutex.h
index ccd025c98a2..bf098850401 100644
--- a/include/tsutil/TsSharedMutex.h
+++ b/include/tsutil/TsSharedMutex.h
@@ -27,6 +27,7 @@
#include
#include "tsutil/Strerror.h"
#include "tsutil/Assert.h"
+#include "tsutil/ts_thread_safety.h"
#ifdef X
#error "X preprocessor symbol defined"
@@ -48,7 +49,7 @@ namespace ts
{
// A class with the same interface as std::shared_mutex, but which is not prone to writer starvation.
//
-class shared_mutex
+class TS_CAPABILITY("shared_mutex") shared_mutex
{
public:
shared_mutex() {}
@@ -58,8 +59,13 @@ class shared_mutex
shared_mutex(shared_mutex const &) = delete;
shared_mutex &operator=(shared_mutex const &) = delete;
+ // The lock/unlock methods are the trusted implementation of this capability:
+ // their bodies drive the raw pthread_rwlock_t, which some libc headers (e.g.
+ // FreeBSD's ) annotate as a capability in its own right. Exempt the
+ // bodies from analysis so only the capability contract on each signature is
+ // checked; the actual data-race checking happens at the call sites.
void
- lock()
+ lock() TS_ACQUIRE() TS_NO_THREAD_SAFETY_ANALYSIS
{
int error = pthread_rwlock_wrlock(&_lock);
if (error != 0) {
@@ -69,7 +75,7 @@ class shared_mutex
}
bool
- try_lock()
+ try_lock() TS_TRY_ACQUIRE(true) TS_NO_THREAD_SAFETY_ANALYSIS
{
int error = pthread_rwlock_trywrlock(&_lock);
if (EBUSY == error) {
@@ -84,7 +90,7 @@ class shared_mutex
}
void
- unlock()
+ unlock() TS_RELEASE() TS_NO_THREAD_SAFETY_ANALYSIS
{
X(debug_assert(_exclusive);)
X(_exclusive = false;)
@@ -93,7 +99,7 @@ class shared_mutex
}
void
- lock_shared()
+ lock_shared() TS_ACQUIRE_SHARED() TS_NO_THREAD_SAFETY_ANALYSIS
{
int error = pthread_rwlock_rdlock(&_lock);
if (error != 0) {
@@ -105,7 +111,7 @@ class shared_mutex
}
bool
- try_lock_shared()
+ try_lock_shared() TS_TRY_ACQUIRE_SHARED(true) TS_NO_THREAD_SAFETY_ANALYSIS
{
int error = pthread_rwlock_tryrdlock(&_lock);
if (EBUSY == error) {
@@ -121,7 +127,7 @@ class shared_mutex
}
void
- unlock_shared()
+ unlock_shared() TS_RELEASE_SHARED() TS_NO_THREAD_SAFETY_ANALYSIS
{
X(debug_assert(_shared > 0);)
X(--_shared;)
@@ -148,7 +154,7 @@ class shared_mutex
private:
void
- _unlock()
+ _unlock() TS_NO_THREAD_SAFETY_ANALYSIS
{
int error = pthread_rwlock_unlock(&_lock);
if (error != 0) {
@@ -178,6 +184,38 @@ class shared_mutex
X(std::atomic _shared{0};)
};
+// RAII guards for ts::shared_mutex that carry Clang thread-safety capability
+// state. Prefer these over std::unique_lock / std::shared_lock in code annotated
+// for -Wthread-safety: the analysis does not reliably track the std wrappers
+// (see tsutil/ts_thread_safety.h).
+//
+class TS_SCOPED_CAPABILITY write_guard
+{
+public:
+ explicit write_guard(shared_mutex &m) TS_ACQUIRE(m) : _m(m) { _m.lock(); }
+ ~write_guard() TS_RELEASE() { _m.unlock(); }
+
+ write_guard(write_guard const &) = delete;
+ write_guard &operator=(write_guard const &) = delete;
+
+private:
+ shared_mutex &_m;
+};
+
+class TS_SCOPED_CAPABILITY read_guard
+{
+public:
+ explicit read_guard(shared_mutex &m) TS_ACQUIRE_SHARED(m) : _m(m) { _m.lock_shared(); }
+ // A scoped-capability destructor uses the plain release form even for a shared acquire.
+ ~read_guard() TS_RELEASE() { _m.unlock_shared(); }
+
+ read_guard(read_guard const &) = delete;
+ read_guard &operator=(read_guard const &) = delete;
+
+private:
+ shared_mutex &_m;
+};
+
} // end namespace ts
#undef X
diff --git a/include/tsutil/ts_thread_safety.h b/include/tsutil/ts_thread_safety.h
new file mode 100644
index 00000000000..36e3b36e4ef
--- /dev/null
+++ b/include/tsutil/ts_thread_safety.h
@@ -0,0 +1,97 @@
+/** @file
+
+ Clang Thread Safety Analysis annotation macros.
+
+ These wrap Clang's @c -Wthread-safety attributes so a lock's contract can be
+ expressed in the type system: which mutex guards which data, and which lock a
+ function requires its caller to hold. The compiler then proves, at build time,
+ that guarded data is only touched while the right lock is held.
+
+ The annotations are compile-time only. Under Clang they expand to
+ @c __attribute__((...)) consumed by the analysis; under every other compiler
+ they expand to nothing. They generate no code and have zero runtime cost.
+
+ Important note: Clang's analysis only tracks lock state through types marked as
+ capabilities (the mutex) and scoped capabilities (the RAII guard). The @c std::
+ lock wrappers are deliberately flexible -- deferred locking, adopt/release, and
+ movability let the held state escape a single scope -- which makes it impossible
+ to track statically, and ATS does not need that flexibility. Annotated code
+ therefore takes its locks through ATS-owned annotated types -- @c ts::mutex with
+ @c ts::lock_guard, or @c ts::shared_mutex with @c ts::write_guard /
+ @c ts::read_guard -- whose simple acquire-in-constructor /
+ release-in-destructor contract the analysis can follow.
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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.
+ */
+
+#pragma once
+
+#if defined(__clang__)
+#define TS_THREAD_ANNOTATION(x) __attribute__((x))
+#else
+#define TS_THREAD_ANNOTATION(x)
+#endif
+
+/// Mark a class as a capability (a lock-like object that can be "held").
+#define TS_CAPABILITY(name) TS_THREAD_ANNOTATION(capability(name))
+
+/// Mark an RAII type whose lifetime holds a capability (acquire in ctor,
+/// release in dtor), e.g. a scoped lock guard.
+#define TS_SCOPED_CAPABILITY TS_THREAD_ANNOTATION(scoped_lockable)
+
+/// The annotated data member may only be accessed while @a x is held.
+#define TS_GUARDED_BY(x) TS_THREAD_ANNOTATION(guarded_by(x))
+
+/// The data pointed to by the annotated pointer may only be accessed while
+/// @a x is held.
+#define TS_PT_GUARDED_BY(x) TS_THREAD_ANNOTATION(pt_guarded_by(x))
+
+/// The function acquires the listed capabilities (exclusively / shared).
+#define TS_ACQUIRE(...) TS_THREAD_ANNOTATION(acquire_capability(__VA_ARGS__))
+#define TS_ACQUIRE_SHARED(...) TS_THREAD_ANNOTATION(acquire_shared_capability(__VA_ARGS__))
+
+/// The function releases the listed capabilities. Use the plain form (not the
+/// shared form) on a scoped-capability destructor even for a shared guard.
+#define TS_RELEASE(...) TS_THREAD_ANNOTATION(release_capability(__VA_ARGS__))
+#define TS_RELEASE_SHARED(...) TS_THREAD_ANNOTATION(release_shared_capability(__VA_ARGS__))
+
+/// The function conditionally acquires a capability, holding it only on the
+/// branch where it returns @a success_value.
+#define TS_TRY_ACQUIRE(...) TS_THREAD_ANNOTATION(try_acquire_capability(__VA_ARGS__))
+#define TS_TRY_ACQUIRE_SHARED(...) TS_THREAD_ANNOTATION(try_acquire_shared_capability(__VA_ARGS__))
+
+/// The caller must already hold the listed capabilities (exclusively / shared).
+#define TS_REQUIRES(...) TS_THREAD_ANNOTATION(requires_capability(__VA_ARGS__))
+#define TS_REQUIRES_SHARED(...) TS_THREAD_ANNOTATION(requires_shared_capability(__VA_ARGS__))
+
+/// The caller must NOT hold the listed capabilities (prevents self-deadlock).
+#define TS_EXCLUDES(...) TS_THREAD_ANNOTATION(locks_excluded(__VA_ARGS__))
+
+/// Declare that a getter returns a reference to the named capability.
+#define TS_RETURN_CAPABILITY(x) TS_THREAD_ANNOTATION(lock_returned(x))
+
+/// Tell the analysis a capability is held here without acquiring it (runtime
+/// assertion form).
+#define TS_ASSERT_CAPABILITY(x) TS_THREAD_ANNOTATION(assert_capability(x))
+#define TS_ASSERT_SHARED_CAPABILITY(x) TS_THREAD_ANNOTATION(assert_shared_capability(x))
+
+/// Disable the analysis for a single function. Use sparingly, for code the
+/// analysis cannot model (recursive acquire, hand-off across threads,
+/// single-threaded destructors).
+#define TS_NO_THREAD_SAFETY_ANALYSIS TS_THREAD_ANNOTATION(no_thread_safety_analysis)
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index 6e28a50eb15..17771e4ee5f 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -78,3 +78,40 @@ add_library(systemtap::systemtap INTERFACE IMPORTED GLOBAL)
target_include_directories(systemtap::systemtap INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/systemtap")
add_subdirectory(ls-hpack)
+
+# Currently v1.4.0
+#
+# Upstream: https://github.com/google/highway
+#
+# Source-only, preserves the readme, license, cmake
+# infrastructure and main source; does not include
+# examples, tests or documentation.
+#
+# Note that hwy/tests/list_targets.cc is retained
+# for compiled-in target information
+#
+# CMakeLists.txt slightly modified to fully disable
+# building tests if `HWY_ENABLE_TESTS=OFF`, we intend
+# to upstream this change
+if(NOT EXTERNAL_HWY)
+ message(STATUS "Using internal highway")
+ set(HWY_FORCE_STATIC_LIBS
+ ON
+ CACHE BOOL "Ignore BUILD_SHARED_LIBS" FORCE
+ )
+ set(HWY_ENABLE_INSTALL
+ OFF
+ CACHE BOOL "Install library" FORCE
+ )
+ set(HWY_ENABLE_EXAMPLES
+ OFF
+ CACHE BOOL "Build examples" FORCE
+ )
+ set(HWY_ENABLE_TESTS
+ OFF
+ CACHE BOOL "Enable HWY tests" FORCE
+ )
+ add_subdirectory(highway)
+ add_library(hwy::hwy ALIAS hwy)
+ add_library(hwy::hwy_contrib ALIAS hwy_contrib)
+endif()
diff --git a/lib/fastlz/README.md b/lib/fastlz/README.md
index 6ec851ac909..9d7bf696b5b 100644
--- a/lib/fastlz/README.md
+++ b/lib/fastlz/README.md
@@ -34,31 +34,49 @@ For [Vcpkg](https://github.com/microsoft/vcpkg) users, FastLZ is [already availa
A simple file compressor called `6pack` is included as an example on how to use FastLZ. The corresponding decompressor is `6unpack`.
-FastLZ supports any standard-conforming ANSI C/C90 compiler, including the popular ones such as GCC, Clang, Intel C++ Compiler, Visual Studio and even Tiny CC. FastLZ works well on a number of architectures (32-bit and 64-bit, big endian and little endian), from Intel/AMD, ARM, and MIPS.
+FastLZ supports any standard-conforming ANSI C/C90 compiler, including the popular ones such as [GCC](https://gcc.gnu.org/), [Clang](https://clang.llvm.org/), [Visual Studio](https://visualstudio.microsoft.com/vs/features/cplusplus/), and even [Tiny CC](https://bellard.org/tcc/). FastLZ works well on a number of architectures (32-bit and 64-bit, big endian and little endian), from Intel/AMD, PowerPC, System z, ARM, MIPS, and RISC-V.
The continuous integration system runs an extensive set of compression-decompression round trips on the following systems:
For more details, check the corresponding [GitHub Actions build logs](https://github.com/ariya/FastLZ/actions).
-| | | | |
-|--------------|---------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------|
-| **amd64** | **Linux** | **Windows** | **macOS** |
-| GCC |  |  |  |
-| Clang |  |  |  |
-| Intel CC |  | | |
-| TinyCC |  | | | |
-| VS 2017 | |  | |
-| VS 2019 | |  | |
-| **i686** | **Linux** | **Windows** | **macOS** |
-| GCC |  | | |
-| Clang |  | | |
-| VS 2017 | |  | |
-| VS 2019 | |  | |
-| **arm64** | **Linux** | **Windows** | **macOS** |
-| GCC |  | | |
-| **mips64** | **Linux** | **Windows** | **macOS** |
-| GCC |  | | |
+| | | | |
+|----------------------|--------------------------------------------------------------------------------------------------------:|--------------------------------------------------------------------------------------------------:|--------------------------------------------------------------------------------------------:|
+| **amd64** | **Linux** | **Windows** | **macOS** |
+| GCC |  |  |  |
+| Clang |  |  |  |
+| TinyCC |  |  | |
+| VS 2019 | |  | |
+| **i686** | **Linux** | **Windows** | **macOS** |
+| GCC |  | | |
+| Clang |  | | |
+| TinyCC | |  | |
+| VS 2019 | |  | |
+| **i586** | **Linux** | **DOS** | |
+| GCC | |  | |
+| | **Linux** | | |
+| **powerpc** | | | |
+| GCC |  | | |
+| **ppc64(le)** | | | |
+| GCC |  | | |
+| GCC |  | | |
+| **s390x** | | | |
+| GCC |  | | |
+| **armhf** | | | |
+| GCC |  | | |
+| **arm64** | | | |
+| GCC |  | | |
+| **mips(el)** | | | |
+| GCC |  | | |
+| GCC |  | | |
+| **mips64(el)** | | | |
+| GCC |  | | |
+| GCC |  | | |
+| **riscv** | | | |
+| GCC |  | | |
+| **riscv64** | | | |
+| GCC |  | | |
@@ -66,7 +84,7 @@ For more details, check the corresponding [GitHub Actions build logs](https://gi
Let us assume that FastLZ compresses an array of bytes, called the _uncompressed block_, into another array of bytes, called the _compressed block_. To understand what will be stored in the compressed block, it is illustrative to demonstrate how FastLZ will _decompress_ the block to retrieve the original uncompressed block.
-The first 5-bit of the block, i.e. the 5 most-significant bits of the first byte, is the **block tag**. Currently the block tag determines the compression level used to produce the compressed block.
+The first 3-bit of the block, i.e. the 3 most-significant bits of the first byte, is the **block tag**. Currently the block tag determines the compression level used to produce the compressed block.
|Block tag|Compression level|
|---------|-----------------|
@@ -77,16 +95,16 @@ The content of the block will vary depending on the compression level.
### Block Format for Level 1
-FastLZ Level 1 impements LZ77 compression algorithm with 8 KB sliding window and up to 264 bytes of match length.
+FastLZ Level 1 implements LZ77 compression algorithm with 8 KB sliding window and up to 264 bytes of match length.
The compressed block consists of one or more **instructions**.
Each instruction starts with a 1-byte opcode, 2-byte opcode, or 3-byte opcode.
| Instruction type | Opcode[0] | Opcode[1] | Opcode[2]
|-----------|------------------|--------------------|--|
-| Literal run | `000`, L₅-L₀ | -|- |
+| Literal run | `000`, L₄-L₀ | -|- |
| Short match | M₂-M₀, R₁₂-R₈ | R₇-R₀ | - |
-| Long match | `111`, R₁₂-R₈ | M₇-R₀ | R₇-R₀ |
+| Long match | `111`, R₁₂-R₈ | M₇-M₀ | R₇-R₀ |
Note that the _very first_ instruction in a compressed block is always a literal run.
diff --git a/lib/fastlz/fastlz.cc b/lib/fastlz/fastlz.cc
index 2e9bfb3d72d..f99bb10b0f3 100644
--- a/lib/fastlz/fastlz.cc
+++ b/lib/fastlz/fastlz.cc
@@ -25,14 +25,8 @@
#include
-/*
- * Always check for bound when decompressing.
- * Generally it is best to leave it defined.
- */
-#define FASTLZ_SAFE
-#if defined(FASTLZ_USE_SAFE_DECOMPRESSOR) && (FASTLZ_USE_SAFE_DECOMPRESSOR == 0)
-#undef FASTLZ_SAFE
-#endif
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
/*
* Give hints to the compiler for branch prediction optimization.
@@ -48,33 +42,26 @@
/*
* Specialize custom 64-bit implementation for speed improvements.
*/
-#if defined(__x86_64__) || defined(_M_X64)
+#if defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__)
#define FLZ_ARCH64
#endif
-#if defined(FASTLZ_SAFE)
-#define FASTLZ_BOUND_CHECK(cond) \
- if (FASTLZ_UNLIKELY(!(cond))) \
- return 0;
-#else
-#define FASTLZ_BOUND_CHECK(cond) \
- do { \
- } while (0)
+/*
+ * Workaround for DJGPP to find uint8_t, uint16_t, etc.
+ */
+#if defined(__MSDOS__) && defined(__GNUC__)
+#include
#endif
#if defined(FASTLZ_USE_MEMMOVE) && (FASTLZ_USE_MEMMOVE == 0)
-static void
-fastlz_memmove(uint8_t *dest, const uint8_t *src, uint32_t count)
-{
+static void fastlz_memmove(uint8_t* dest, const uint8_t* src, uint32_t count) {
do {
*dest++ = *src++;
} while (--count);
}
-static void
-fastlz_memcpy(uint8_t *dest, const uint8_t *src, uint32_t count)
-{
+static void fastlz_memcpy(uint8_t* dest, const uint8_t* src, uint32_t count) {
return fastlz_memmove(dest, src, count);
}
@@ -82,146 +69,64 @@ fastlz_memcpy(uint8_t *dest, const uint8_t *src, uint32_t count)
#include
-static void
-fastlz_memmove(uint8_t *dest, const uint8_t *src, uint32_t count)
-{
+static void fastlz_memmove(uint8_t* dest, const uint8_t* src, uint32_t count) {
if ((count > 4) && (dest >= src + count)) {
memmove(dest, src, count);
} else {
switch (count) {
- default:
- do {
+ default:
+ do {
+ *dest++ = *src++;
+ } while (--count);
+ break;
+ case 3:
+ *dest++ = *src++;
+ case 2:
*dest++ = *src++;
- } while (--count);
- break;
- case 3:
- *dest++ = *src++;
- [[fallthrough]];
- case 2:
- *dest++ = *src++;
- [[fallthrough]];
- case 1:
- *dest++ = *src++;
- [[fallthrough]];
- case 0:
- break;
+ case 1:
+ *dest++ = *src++;
+ case 0:
+ break;
}
}
}
-static void
-fastlz_memcpy(uint8_t *dest, const uint8_t *src, uint32_t count)
-{
- memcpy(dest, src, count);
-}
+static void fastlz_memcpy(uint8_t* dest, const uint8_t* src, uint32_t count) { memcpy(dest, src, count); }
#endif
#if defined(FLZ_ARCH64)
-static uint32_t
-flz_readu32(const void *ptr)
-{
- return *(const uint32_t *)ptr;
-}
-
-static uint64_t
-flz_readu64(const void *ptr)
-{
- return *(const uint64_t *)ptr;
-}
+static uint32_t flz_readu32(const void* ptr) { return *(const uint32_t*)ptr; }
-static uint32_t
-flz_cmp(const uint8_t *p, const uint8_t *q, const uint8_t *r)
-{
- const uint8_t *start = p;
+static uint32_t flz_cmp(const uint8_t* p, const uint8_t* q, const uint8_t* r) {
+ const uint8_t* start = p;
- if (flz_readu64(p) == flz_readu64(q)) {
- p += 8;
- q += 8;
- }
if (flz_readu32(p) == flz_readu32(q)) {
p += 4;
q += 4;
}
while (q < r)
- if (*p++ != *q++)
- break;
+ if (*p++ != *q++) break;
return p - start;
}
-static void
-flz_copy64(uint8_t *dest, const uint8_t *src, uint32_t count)
-{
- const uint64_t *p = (const uint64_t *)src;
- uint64_t *q = (uint64_t *)dest;
- if (count < 16) {
- if (count >= 8) {
- *q++ = *p++;
- }
- *q++ = *p++;
- } else {
- *q++ = *p++;
- *q++ = *p++;
- *q++ = *p++;
- *q++ = *p++;
- }
-}
-
-static void
-flz_copy256(void *dest, const void *src)
-{
- const uint64_t *p = (const uint64_t *)src;
- uint64_t *q = (uint64_t *)dest;
- *q++ = *p++;
- *q++ = *p++;
- *q++ = *p++;
- *q++ = *p++;
-}
-
#endif /* FLZ_ARCH64 */
#if !defined(FLZ_ARCH64)
-static uint32_t
-flz_readu32(const void *ptr)
-{
- const uint8_t *p = (const uint8_t *)ptr;
+static uint32_t flz_readu32(const void* ptr) {
+ const uint8_t* p = (const uint8_t*)ptr;
return (p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0];
}
-static uint32_t
-flz_cmp(const uint8_t *p, const uint8_t *q, const uint8_t *r)
-{
- const uint8_t *start = p;
+static uint32_t flz_cmp(const uint8_t* p, const uint8_t* q, const uint8_t* r) {
+ const uint8_t* start = p;
while (q < r)
- if (*p++ != *q++)
- break;
+ if (*p++ != *q++) break;
return p - start;
}
-static void
-flz_copy64(uint8_t *dest, const uint8_t *src, uint32_t count)
-{
- const uint8_t *p = (const uint8_t *)src;
- uint8_t *q = (uint8_t *)dest;
- unsigned int c;
- for (c = 0; c < count * 8; ++c) {
- *q++ = *p++;
- }
-}
-
-static void
-flz_copy256(void *dest, const void *src)
-{
- const uint8_t *p = (const uint8_t *)src;
- uint8_t *q = (uint8_t *)dest;
- int c;
- for (c = 0; c < 32; ++c) {
- *q++ = *p++;
- }
-}
-
#endif /* !FLZ_ARCH64 */
#define MAX_COPY 32
@@ -230,60 +135,54 @@ flz_copy256(void *dest, const void *src)
#define MAX_L2_DISTANCE 8191
#define MAX_FARDISTANCE (65535 + MAX_L2_DISTANCE - 1)
-#define HASH_LOG 14
+#define HASH_LOG 13
#define HASH_SIZE (1 << HASH_LOG)
#define HASH_MASK (HASH_SIZE - 1)
-static uint16_t
-flz_hash(uint32_t v)
-{
+static uint16_t flz_hash(uint32_t v) {
uint32_t h = (v * 2654435769LL) >> (32 - HASH_LOG);
return h & HASH_MASK;
}
-static uint8_t *
-flz_literals(uint32_t runs, const uint8_t *src, uint8_t *dest)
-{
- while (runs >= MAX_COPY) {
- *dest++ = MAX_COPY - 1;
- flz_copy256(dest, src);
- src += MAX_COPY;
- dest += MAX_COPY;
- runs -= MAX_COPY;
- }
- if (runs > 0) {
- *dest++ = runs - 1;
- flz_copy64(dest, src, runs);
- dest += runs;
- }
- return dest;
-}
-
-/* special case of memcpy: at most 32 bytes */
-static void
-flz_smallcopy(uint8_t *dest, const uint8_t *src, uint32_t count)
-{
+/* special case of memcpy: at most MAX_COPY bytes */
+static void flz_smallcopy(uint8_t* dest, const uint8_t* src, uint32_t count) {
#if defined(FLZ_ARCH64)
- if (count >= 8) {
- const uint64_t *p = (const uint64_t *)src;
- uint64_t *q = (uint64_t *)dest;
- while (count > 8) {
+ if (count >= 4) {
+ const uint32_t* p = (const uint32_t*)src;
+ uint32_t* q = (uint32_t*)dest;
+ while (count > 4) {
*q++ = *p++;
- count -= 8;
- dest += 8;
- src += 8;
+ count -= 4;
+ dest += 4;
+ src += 4;
}
}
#endif
fastlz_memcpy(dest, src, count);
}
-static uint8_t *
-flz_finalize(uint32_t runs, const uint8_t *src, uint8_t *dest)
-{
+/* special case of memcpy: exactly MAX_COPY bytes */
+static void flz_maxcopy(void* dest, const void* src) {
+#if defined(FLZ_ARCH64)
+ const uint32_t* p = (const uint32_t*)src;
+ uint32_t* q = (uint32_t*)dest;
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = *p++;
+#else
+ fastlz_memcpy(dest, src, MAX_COPY);
+#endif
+}
+
+static uint8_t* flz_literals(uint32_t runs, const uint8_t* src, uint8_t* dest) {
while (runs >= MAX_COPY) {
*dest++ = MAX_COPY - 1;
- flz_smallcopy(dest, src, MAX_COPY);
+ flz_maxcopy(dest, src);
src += MAX_COPY;
dest += MAX_COPY;
runs -= MAX_COPY;
@@ -296,9 +195,7 @@ flz_finalize(uint32_t runs, const uint8_t *src, uint8_t *dest)
return dest;
}
-static uint8_t *
-flz1_match(uint32_t len, uint32_t distance, uint8_t *op)
-{
+static uint8_t* flz1_match(uint32_t len, uint32_t distance, uint8_t* op) {
--distance;
if (FASTLZ_UNLIKELY(len > MAX_LEN - 2))
while (len > MAX_LEN - 2) {
@@ -318,46 +215,44 @@ flz1_match(uint32_t len, uint32_t distance, uint8_t *op)
return op;
}
-int
-fastlz1_compress(const void *input, int length, void *output)
-{
- const uint8_t *ip = (const uint8_t *)input;
- const uint8_t *ip_start = ip;
- const uint8_t *ip_bound = ip + length - 4; /* because readU32 */
- const uint8_t *ip_limit = ip + length - 12 - 1;
- uint8_t *op = (uint8_t *)output;
+#define FASTLZ_BOUND_CHECK(cond) \
+ if (FASTLZ_UNLIKELY(!(cond))) return 0;
+
+static int fastlz1_compress(const void* input, int length, void* output) {
+ const uint8_t* ip = (const uint8_t*)input;
+ const uint8_t* ip_start = ip;
+ const uint8_t* ip_bound = ip + length - 4; /* because readU32 */
+ const uint8_t* ip_limit = ip + length - 12 - 1;
+ uint8_t* op = (uint8_t*)output;
uint32_t htab[HASH_SIZE];
uint32_t seq, hash;
/* initializes hash table */
- for (hash = 0; hash < HASH_SIZE; ++hash)
- htab[hash] = 0;
+ for (hash = 0; hash < HASH_SIZE; ++hash) htab[hash] = 0;
/* we start with literal copy */
- const uint8_t *anchor = ip;
+ const uint8_t* anchor = ip;
ip += 2;
/* main loop */
while (FASTLZ_LIKELY(ip < ip_limit)) {
- const uint8_t *ref;
+ const uint8_t* ref;
uint32_t distance, cmp;
/* find potential match */
do {
- seq = flz_readu32(ip) & 0xffffff;
- hash = flz_hash(seq);
- ref = ip_start + htab[hash];
+ seq = flz_readu32(ip) & 0xffffff;
+ hash = flz_hash(seq);
+ ref = ip_start + htab[hash];
htab[hash] = ip - ip_start;
- distance = ip - ref;
- cmp = FASTLZ_LIKELY(distance < MAX_L1_DISTANCE) ? flz_readu32(ref) & 0xffffff : 0x1000000;
- if (FASTLZ_UNLIKELY(ip >= ip_limit))
- break;
+ distance = ip - ref;
+ cmp = FASTLZ_LIKELY(distance < MAX_L1_DISTANCE) ? flz_readu32(ref) & 0xffffff : 0x1000000;
+ if (FASTLZ_UNLIKELY(ip >= ip_limit)) break;
++ip;
} while (seq != cmp);
- if (FASTLZ_UNLIKELY(ip >= ip_limit))
- break;
+ if (FASTLZ_UNLIKELY(ip >= ip_limit)) break;
--ip;
if (FASTLZ_LIKELY(ip > anchor)) {
@@ -365,41 +260,39 @@ fastlz1_compress(const void *input, int length, void *output)
}
uint32_t len = flz_cmp(ref + 3, ip + 3, ip_bound);
- op = flz1_match(len, distance, op);
+ op = flz1_match(len, distance, op);
/* update the hash at match boundary */
ip += len;
- seq = flz_readu32(ip);
- hash = flz_hash(seq & 0xffffff);
+ seq = flz_readu32(ip);
+ hash = flz_hash(seq & 0xffffff);
htab[hash] = ip++ - ip_start;
seq >>= 8;
- hash = flz_hash(seq);
+ hash = flz_hash(seq);
htab[hash] = ip++ - ip_start;
anchor = ip;
}
- uint32_t copy = (uint8_t *)input + length - anchor;
- op = flz_finalize(copy, anchor, op);
+ uint32_t copy = (uint8_t*)input + length - anchor;
+ op = flz_literals(copy, anchor, op);
- return op - (uint8_t *)output;
+ return op - (uint8_t*)output;
}
-int
-fastlz1_decompress(const void *input, int length, void *output, int maxout)
-{
- const uint8_t *ip = (const uint8_t *)input;
- const uint8_t *ip_limit = ip + length;
- const uint8_t *ip_bound = ip_limit - 2;
- uint8_t *op = (uint8_t *)output;
- uint8_t *op_limit = op + maxout;
- uint32_t ctrl = (*ip++) & 31;
+static int fastlz1_decompress(const void* input, int length, void* output, int maxout) {
+ const uint8_t* ip = (const uint8_t*)input;
+ const uint8_t* ip_limit = ip + length;
+ const uint8_t* ip_bound = ip_limit - 2;
+ uint8_t* op = (uint8_t*)output;
+ uint8_t* op_limit = op + maxout;
+ uint32_t ctrl = (*ip++) & 31;
while (1) {
if (ctrl >= 32) {
- uint32_t len = (ctrl >> 5) - 1;
- uint32_t ofs = (ctrl & 31) << 8;
- const uint8_t *ref = op - ofs - 1;
+ uint32_t len = (ctrl >> 5) - 1;
+ uint32_t ofs = (ctrl & 31) << 8;
+ const uint8_t* ref = op - ofs - 1;
if (len == 7 - 1) {
FASTLZ_BOUND_CHECK(ip <= ip_bound);
len += *ip++;
@@ -407,7 +300,7 @@ fastlz1_decompress(const void *input, int length, void *output, int maxout)
ref -= *ip++;
len += 3;
FASTLZ_BOUND_CHECK(op + len <= op_limit);
- FASTLZ_BOUND_CHECK(ref >= (uint8_t *)output);
+ FASTLZ_BOUND_CHECK(ref >= (uint8_t*)output);
fastlz_memmove(op, ref, len);
op += len;
} else {
@@ -419,17 +312,14 @@ fastlz1_decompress(const void *input, int length, void *output, int maxout)
op += ctrl;
}
- if (FASTLZ_UNLIKELY(ip > ip_bound))
- break;
+ if (FASTLZ_UNLIKELY(ip > ip_bound)) break;
ctrl = *ip++;
}
- return op - (uint8_t *)output;
+ return op - (uint8_t*)output;
}
-static uint8_t *
-flz2_match(uint32_t len, uint32_t distance, uint8_t *op)
-{
+static uint8_t* flz2_match(uint32_t len, uint32_t distance, uint8_t* op) {
--distance;
if (distance < MAX_L2_DISTANCE) {
if (len < 7) {
@@ -437,8 +327,7 @@ flz2_match(uint32_t len, uint32_t distance, uint8_t *op)
*op++ = (distance & 255);
} else {
*op++ = (7 << 5) + (distance >> 8);
- for (len -= 7; len >= 255; len -= 255)
- *op++ = 255;
+ for (len -= 7; len >= 255; len -= 255) *op++ = 255;
*op++ = len;
*op++ = (distance & 255);
}
@@ -453,8 +342,7 @@ flz2_match(uint32_t len, uint32_t distance, uint8_t *op)
} else {
distance -= MAX_L2_DISTANCE;
*op++ = (7 << 5) + 31;
- for (len -= 7; len >= 255; len -= 255)
- *op++ = 255;
+ for (len -= 7; len >= 255; len -= 255) *op++ = 255;
*op++ = len;
*op++ = 255;
*op++ = distance >> 8;
@@ -464,46 +352,41 @@ flz2_match(uint32_t len, uint32_t distance, uint8_t *op)
return op;
}
-int
-fastlz2_compress(const void *input, int length, void *output)
-{
- const uint8_t *ip = (const uint8_t *)input;
- const uint8_t *ip_start = ip;
- const uint8_t *ip_bound = ip + length - 4; /* because readU32 */
- const uint8_t *ip_limit = ip + length - 12 - 1;
- uint8_t *op = (uint8_t *)output;
+static int fastlz2_compress(const void* input, int length, void* output) {
+ const uint8_t* ip = (const uint8_t*)input;
+ const uint8_t* ip_start = ip;
+ const uint8_t* ip_bound = ip + length - 4; /* because readU32 */
+ const uint8_t* ip_limit = ip + length - 12 - 1;
+ uint8_t* op = (uint8_t*)output;
uint32_t htab[HASH_SIZE];
uint32_t seq, hash;
/* initializes hash table */
- for (hash = 0; hash < HASH_SIZE; ++hash)
- htab[hash] = 0;
+ for (hash = 0; hash < HASH_SIZE; ++hash) htab[hash] = 0;
/* we start with literal copy */
- const uint8_t *anchor = ip;
+ const uint8_t* anchor = ip;
ip += 2;
/* main loop */
while (FASTLZ_LIKELY(ip < ip_limit)) {
- const uint8_t *ref;
+ const uint8_t* ref;
uint32_t distance, cmp;
/* find potential match */
do {
- seq = flz_readu32(ip) & 0xffffff;
- hash = flz_hash(seq);
- ref = ip_start + htab[hash];
+ seq = flz_readu32(ip) & 0xffffff;
+ hash = flz_hash(seq);
+ ref = ip_start + htab[hash];
htab[hash] = ip - ip_start;
- distance = ip - ref;
- cmp = FASTLZ_LIKELY(distance < MAX_FARDISTANCE) ? flz_readu32(ref) & 0xffffff : 0x1000000;
- if (FASTLZ_UNLIKELY(ip >= ip_limit))
- break;
+ distance = ip - ref;
+ cmp = FASTLZ_LIKELY(distance < MAX_FARDISTANCE) ? flz_readu32(ref) & 0xffffff : 0x1000000;
+ if (FASTLZ_UNLIKELY(ip >= ip_limit)) break;
++ip;
} while (seq != cmp);
- if (FASTLZ_UNLIKELY(ip >= ip_limit))
- break;
+ if (FASTLZ_UNLIKELY(ip >= ip_limit)) break;
--ip;
@@ -520,48 +403,45 @@ fastlz2_compress(const void *input, int length, void *output)
}
uint32_t len = flz_cmp(ref + 3, ip + 3, ip_bound);
- op = flz2_match(len, distance, op);
+ op = flz2_match(len, distance, op);
/* update the hash at match boundary */
ip += len;
- seq = flz_readu32(ip);
- hash = flz_hash(seq & 0xffffff);
+ seq = flz_readu32(ip);
+ hash = flz_hash(seq & 0xffffff);
htab[hash] = ip++ - ip_start;
seq >>= 8;
- hash = flz_hash(seq);
+ hash = flz_hash(seq);
htab[hash] = ip++ - ip_start;
anchor = ip;
}
- uint32_t copy = (uint8_t *)input + length - anchor;
- op = flz_finalize(copy, anchor, op);
+ uint32_t copy = (uint8_t*)input + length - anchor;
+ op = flz_literals(copy, anchor, op);
/* marker for fastlz2 */
- *(uint8_t *)output |= (1 << 5);
+ *(uint8_t*)output |= (1 << 5);
- return op - (uint8_t *)output;
+ return op - (uint8_t*)output;
}
-int
-fastlz2_decompress(const void *input, int length, void *output, int maxout)
-{
- const uint8_t *ip = (const uint8_t *)input;
- const uint8_t *ip_limit = ip + length;
- const uint8_t *ip_bound = ip_limit - 2;
- uint8_t *op = (uint8_t *)output;
- uint8_t *op_limit = op + maxout;
- uint32_t ctrl = (*ip++) & 31;
+static int fastlz2_decompress(const void* input, int length, void* output, int maxout) {
+ const uint8_t* ip = (const uint8_t*)input;
+ const uint8_t* ip_limit = ip + length;
+ const uint8_t* ip_bound = ip_limit - 2;
+ uint8_t* op = (uint8_t*)output;
+ uint8_t* op_limit = op + maxout;
+ uint32_t ctrl = (*ip++) & 31;
while (1) {
if (ctrl >= 32) {
- uint32_t len = (ctrl >> 5) - 1;
- uint32_t ofs = (ctrl & 31) << 8;
- const uint8_t *ref = op - ofs - 1;
+ uint32_t len = (ctrl >> 5) - 1;
+ uint32_t ofs = (ctrl & 31) << 8;
+ const uint8_t* ref = op - ofs - 1;
uint8_t code;
- if (len == 7 - 1)
- do {
+ if (len == 7 - 1) do {
FASTLZ_BOUND_CHECK(ip <= ip_bound);
code = *ip++;
len += code;
@@ -580,7 +460,7 @@ fastlz2_decompress(const void *input, int length, void *output, int maxout)
}
FASTLZ_BOUND_CHECK(op + len <= op_limit);
- FASTLZ_BOUND_CHECK(ref >= (uint8_t *)output);
+ FASTLZ_BOUND_CHECK(ref >= (uint8_t*)output);
fastlz_memmove(op, ref, len);
op += len;
} else {
@@ -592,47 +472,37 @@ fastlz2_decompress(const void *input, int length, void *output, int maxout)
op += ctrl;
}
- if (FASTLZ_UNLIKELY(ip >= ip_limit))
- break;
+ if (FASTLZ_UNLIKELY(ip >= ip_limit)) break;
ctrl = *ip++;
}
- return op - (uint8_t *)output;
+ return op - (uint8_t*)output;
}
-int
-fastlz_compress(const void *input, int length, void *output)
-{
+int fastlz_compress(const void* input, int length, void* output) {
/* for short block, choose fastlz1 */
- if (length < 65536)
- return fastlz1_compress(input, length, output);
+ if (length < 65536) return fastlz1_compress(input, length, output);
/* else... */
return fastlz2_compress(input, length, output);
}
-int
-fastlz_decompress(const void *input, int length, void *output, int maxout)
-{
+int fastlz_decompress(const void* input, int length, void* output, int maxout) {
/* magic identifier for compression level */
- int level = ((*(const uint8_t *)input) >> 5) + 1;
+ int level = ((*(const uint8_t*)input) >> 5) + 1;
- if (level == 1)
- return fastlz1_decompress(input, length, output, maxout);
- if (level == 2)
- return fastlz2_decompress(input, length, output, maxout);
+ if (level == 1) return fastlz1_decompress(input, length, output, maxout);
+ if (level == 2) return fastlz2_decompress(input, length, output, maxout);
/* unknown level, trigger error */
return 0;
}
-int
-fastlz_compress_level(int level, const void *input, int length, void *output)
-{
- if (level == 1)
- return fastlz1_compress(input, length, output);
- if (level == 2)
- return fastlz2_compress(input, length, output);
+int fastlz_compress_level(int level, const void* input, int length, void* output) {
+ if (level == 1) return fastlz1_compress(input, length, output);
+ if (level == 2) return fastlz2_compress(input, length, output);
return 0;
}
+
+#pragma GCC diagnostic pop
diff --git a/lib/fastlz/fastlz.h b/lib/fastlz/fastlz.h
index fd41e961e04..9172d74de10 100644
--- a/lib/fastlz/fastlz.h
+++ b/lib/fastlz/fastlz.h
@@ -32,6 +32,10 @@
#define FASTLZ_VERSION_STRING "0.5.0"
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
/**
Compress a block of data in the input buffer and returns the size of
compressed block. The size of input buffer is specified by length. The
@@ -54,7 +58,7 @@
decompressed using the function fastlz_decompress below.
*/
-int fastlz_compress_level(int level, const void *input, int length, void *output);
+int fastlz_compress_level(int level, const void* input, int length, void* output);
/**
Decompress a block of compressed data and returns the size of the
@@ -72,7 +76,7 @@ int fastlz_compress_level(int level, const void *input, int length, void *output
producing the compressed block).
*/
-int fastlz_decompress(const void *input, int length, void *output, int maxout);
+int fastlz_decompress(const void* input, int length, void* output, int maxout);
/**
DEPRECATED.
@@ -84,6 +88,10 @@ int fastlz_decompress(const void *input, int length, void *output, int maxout);
version.
*/
-int fastlz_compress(const void *input, int length, void *output);
+int fastlz_compress(const void* input, int length, void* output);
+
+#if defined(__cplusplus)
+}
+#endif
#endif /* FASTLZ_H */
diff --git a/lib/highway/CMakeLists.txt b/lib/highway/CMakeLists.txt
new file mode 100644
index 00000000000..82acaddc30d
--- /dev/null
+++ b/lib/highway/CMakeLists.txt
@@ -0,0 +1,994 @@
+# Copyright 2019 Google LLC
+# Copyright 2024 Arm Limited and/or its affiliates
+# SPDX-License-Identifier: Apache-2.0
+# SPDX-License-Identifier: BSD-3-Clause
+#
+# 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.
+
+cmake_minimum_required(VERSION 3.10)
+
+# Set PIE flags for POSITION_INDEPENDENT_CODE targets, added in 3.14.
+if(POLICY CMP0083)
+ cmake_policy(SET CMP0083 NEW)
+endif()
+
+# Workaround for 3.19 raising error 'IMPORTED_LOCATION not set for imported
+# target "GTest::gtest_main"'.
+if(POLICY CMP0111)
+ cmake_policy(SET CMP0111 OLD)
+endif()
+
+# Starting with GCC-13, we want to make sure to remove gnu extension (ie.
+# explicit -std=c++17 instead of implicit `gnu++17`)
+# Without this cmake property, CMAKE_CXX_EXTENSIONS=OFF was not properly
+# considered
+if(POLICY CMP0128)
+ cmake_policy(SET CMP0128 NEW)
+endif()
+
+project(hwy VERSION 1.4.0) # Keep in sync with base.h version
+# `hwy` is lowercase to handle find_package() in Config mode:
+set(namespace "${PROJECT_NAME}::")
+
+# Directly define the ABI version from the cmake project() version values:
+set(LIBRARY_VERSION "${hwy_VERSION}")
+set(LIBRARY_SOVERSION ${hwy_VERSION_MAJOR})
+
+set(CMAKE_CXX_EXTENSIONS OFF)
+
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
+# Search for Atomics implementation:
+find_package(Atomics REQUIRED)
+
+# Enabled PIE binaries by default if supported.
+include(CheckPIESupported OPTIONAL RESULT_VARIABLE CHECK_PIE_SUPPORTED)
+if(CHECK_PIE_SUPPORTED)
+ check_pie_supported(LANGUAGES CXX)
+ if(CMAKE_CXX_LINK_PIE_SUPPORTED)
+ set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
+ endif()
+endif()
+
+include(GNUInstallDirs)
+
+if (NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE RelWithDebInfo)
+endif()
+
+# The following is only required with GCC < 6.1.0 or CLANG < 16.0
+set(HWY_CMAKE_ARM7 OFF CACHE BOOL "Set copts for Armv7 with NEON (requires vfpv4)?")
+
+# This must be set on 32-bit x86 with GCC < 13.1, otherwise math_test will be
+# skipped. For GCC 13.1+, you can also build with -fexcess-precision=standard.
+set(HWY_CMAKE_SSE2 OFF CACHE BOOL "Set SSE2 as baseline for 32-bit x86?")
+
+# Currently this will compile the entire codebase with `-march=rvgcv1p0`:
+set(HWY_CMAKE_RVV ON CACHE BOOL "Set copts for RISCV with RVV?")
+
+# Unconditionally adding -Werror risks breaking the build when new warnings
+# arise due to compiler/platform changes. Enable this in CI/tests.
+set(HWY_WARNINGS_ARE_ERRORS OFF CACHE BOOL "Add -Werror flag?")
+
+# Experimental support for header-only builds
+set(HWY_CMAKE_HEADER_ONLY OFF CACHE BOOL "Change to header-only?")
+
+set(HWY_ENABLE_CONTRIB ON CACHE BOOL "Include contrib/")
+set(HWY_ENABLE_EXAMPLES ON CACHE BOOL "Build examples")
+set(HWY_ENABLE_INSTALL ON CACHE BOOL "Install library")
+set(HWY_ENABLE_TESTS ON CACHE BOOL "Enable HWY tests")
+
+if (MSVC)
+set(HWY_TEST_STANDALONE ON CACHE BOOL "Disable use of googletest")
+else()
+set(HWY_TEST_STANDALONE OFF CACHE BOOL "Disable use of googletest")
+endif()
+
+if (NOT DEFINED CMAKE_CXX_STANDARD)
+ if ("cxx_std_17" IN_LIST CMAKE_CXX_COMPILE_FEATURES)
+ set(HWY_CXX_STD_TGT_COMPILE_FEATURE cxx_std_17)
+ else()
+ message(WARNING "cxx_std_17 not found in CMAKE_CXX_COMPILE_FEATURES but vqsort requires C++17")
+ set(HWY_CXX_STD_TGT_COMPILE_FEATURE cxx_std_14)
+ endif()
+else()
+ if (CMAKE_CXX_STANDARD GREATER_EQUAL 17 AND CMAKE_CXX_STANDARD LESS 98)
+ set(HWY_CXX_STD_TGT_COMPILE_FEATURE cxx_std_17)
+ else()
+ message(WARNING "CMAKE_CXX_STANDARD < 17 but vqsort requires C++17")
+ set(HWY_CXX_STD_TGT_COMPILE_FEATURE cxx_std_14)
+ endif()
+endif()
+
+include(CheckCXXSourceCompiles)
+check_cxx_source_compiles(
+ "int main() {
+ #if !defined(__EMSCRIPTEN__)
+ static_assert(false, \"__EMSCRIPTEN__ is not defined\");
+ #endif
+ return 0;
+ }"
+ HWY_EMSCRIPTEN
+)
+
+check_cxx_source_compiles(
+ "int main() {
+ #if !defined(__riscv)
+ static_assert(false, \"__riscv is not defined\");
+ #endif
+ return 0;
+ }"
+ HWY_RISCV
+)
+
+if (WIN32)
+ set (ORIG_CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES})
+ set (CMAKE_REQUIRED_LIBRARIES synchronization)
+ check_cxx_source_compiles(
+ "#ifndef NOMINMAX
+ #define NOMINMAX
+ #endif
+
+ #include
+
+ int main() {
+ unsigned val1 = 0u;
+ unsigned val2 = 1u;
+ WaitOnAddress(&val1, &val2, sizeof(unsigned), 1);
+ WakeByAddressAll(&val1);
+ WakeByAddressSingle(&val1);
+ return 0;
+ }"
+ HWY_HAVE_WIN32_SYNCHRONIZATION_LIB)
+ set (CMAKE_REQUIRED_LIBRARIES ${ORIG_CMAKE_REQUIRED_LIBRARIES})
+else()
+ set (HWY_HAVE_WIN32_SYNCHRONIZATION_LIB OFF)
+endif ()
+
+if (HWY_HAVE_WIN32_SYNCHRONIZATION_LIB OR NOT WIN32)
+ set (HWY_DISABLE_FUTEX OFF CACHE BOOL "Disable futex for thread_pool")
+else()
+ # Force HWY_DISABLE_FUTEX to ON if compiling for Win32 and
+ # libsynchronization.a or synchronization.lib is not available
+ set (HWY_DISABLE_FUTEX ON CACHE BOOL "Disable futex for thread_pool" FORCE)
+endif()
+
+find_package(Threads)
+
+if (NOT Threads_FOUND AND HWY_ENABLE_CONTRIB)
+ message(FATAL_ERROR "Threads must be available if HWY_ENABLE_CONTRIB is ON")
+endif()
+
+if(Threads_FOUND)
+ if(THREADS_HAVE_PTHREAD_ARG)
+ set(HWY_THREAD_FLAGS "-pthread")
+ else()
+ set(HWY_THREAD_FLAGS "")
+ endif()
+
+ if(CMAKE_THREAD_LIBS_INIT)
+ set(HWY_THREAD_LIBS ${CMAKE_THREAD_LIBS_INIT})
+ else()
+ set(HWY_THREAD_LIBS "")
+ endif()
+else()
+ set(HWY_THREAD_FLAGS "")
+ set(HWY_THREAD_LIBS "")
+endif()
+
+if (HWY_RISCV OR CMAKE_CXX_COMPILER_ARCHITECTURE_ID MATCHES "RISCV32|RISCV64|RISCV128" OR CMAKE_SYSTEM_PROCESSOR MATCHES "riscv32|riscv64|riscv128")
+ include(CheckCSourceCompiles)
+ check_c_source_compiles("
+ #if __riscv_xlen == 64
+ int main() { return 0; }
+ #else
+ #error Not RISCV-64
+ #endif
+ " IS_RISCV_XLEN_64)
+
+ check_c_source_compiles("
+ #if __riscv_xlen == 32
+ int main() { return 0; }
+ #else
+ #error Not RISCV-32
+ #endif
+ " IS_RISCV_XLEN_32)
+
+ if(IS_RISCV_XLEN_32)
+ set(RISCV_XLEN 32)
+ elseif(IS_RISCV_XLEN_64)
+ set(RISCV_XLEN 64)
+ else()
+ message(WARNING "Unable to determine RISC-V XLEN")
+ endif()
+endif()
+
+if (HWY_ENABLE_CONTRIB)
+# Glob all the traits so we don't need to modify this file when adding
+# additional special cases.
+file(GLOB HWY_CONTRIB_SOURCES "hwy/contrib/sort/vqsort_*.cc")
+list(APPEND HWY_CONTRIB_SOURCES
+ hwy/contrib/bit_pack/bit_pack-inl.h
+ hwy/contrib/dot/dot-inl.h
+ hwy/contrib/image/image.cc
+ hwy/contrib/image/image.h
+ hwy/contrib/math/fast_math-inl.h
+ hwy/contrib/math/math-inl.h
+ hwy/contrib/matvec/matvec-inl.h
+ hwy/contrib/random/random-inl.h
+ hwy/contrib/sort/order.h
+ hwy/contrib/sort/shared-inl.h
+ hwy/contrib/sort/sorting_networks-inl.h
+ hwy/contrib/sort/traits-inl.h
+ hwy/contrib/sort/traits128-inl.h
+ hwy/contrib/sort/vqsort-inl.h
+ hwy/contrib/sort/vqsort.cc
+ hwy/contrib/sort/vqsort.h
+ hwy/contrib/thread_pool/futex.h
+ hwy/contrib/thread_pool/spin.h
+ hwy/contrib/thread_pool/thread_pool.cc
+ hwy/contrib/thread_pool/thread_pool.h
+ hwy/contrib/thread_pool/topology.cc
+ hwy/contrib/thread_pool/topology.h
+ hwy/contrib/algo/copy-inl.h
+ hwy/contrib/algo/count-inl.h
+ hwy/contrib/algo/find-inl.h
+ hwy/contrib/algo/minmax-inl.h
+ hwy/contrib/algo/transform-inl.h
+ hwy/contrib/unroller/unroller-inl.h
+)
+endif() # HWY_ENABLE_CONTRIB
+
+set(HWY_SOURCES
+ hwy/abort.h
+ hwy/aligned_allocator.h
+ hwy/auto_tune.h
+ hwy/base.h
+ hwy/bit_set.h
+ hwy/cache_control.h
+ hwy/detect_compiler_arch.h # private
+ hwy/detect_targets.h # private
+ hwy/foreach_target.h
+ hwy/highway_export.h
+ hwy/highway.h
+ hwy/nanobenchmark.h
+ hwy/ops/arm_neon-inl.h
+ hwy/ops/arm_sve-inl.h
+ hwy/ops/emu128-inl.h
+ hwy/ops/generic_ops-inl.h
+ hwy/ops/inside-inl.h
+ hwy/ops/loongarch_lsx-inl.h
+ hwy/ops/loongarch_lasx-inl.h
+ hwy/ops/ppc_vsx-inl.h
+ hwy/ops/rvv-inl.h
+ hwy/ops/scalar-inl.h
+ hwy/ops/set_macros-inl.h
+ hwy/ops/shared-inl.h
+ hwy/ops/wasm_128-inl.h
+ hwy/ops/x86_128-inl.h
+ hwy/ops/x86_256-inl.h
+ hwy/ops/x86_512-inl.h
+ hwy/ops/x86_avx3-inl.h
+ hwy/per_target.h
+ hwy/print-inl.h
+ hwy/print.h
+ hwy/profiler.h
+ hwy/robust_statistics.h
+ hwy/targets.h
+ hwy/timer-inl.h
+ hwy/timer.h
+ hwy/x86_cpuid.h
+)
+
+if (NOT HWY_CMAKE_HEADER_ONLY)
+ list(APPEND HWY_SOURCES
+ hwy/abort.cc
+ hwy/aligned_allocator.cc
+ hwy/nanobenchmark.cc
+ hwy/per_target.cc
+ hwy/perf_counters.cc
+ hwy/print.cc
+ hwy/profiler.cc
+ hwy/targets.cc
+ hwy/timer.cc
+ )
+endif()
+
+set(HWY_TEST_SOURCES
+ hwy/tests/hwy_gtest.h
+ hwy/tests/test_util-inl.h
+ hwy/tests/test_util.cc
+ hwy/tests/test_util.h
+)
+
+if (MSVC)
+ set(HWY_FLAGS
+ # fix build error C1128 in blockwise*_test & arithmetic_test
+ /bigobj
+
+ # Warnings
+ /W4
+ # Disable some W4 warnings. Enable them individually after they are cleaned up.
+ /wd4100
+ /wd4127
+ /wd4324
+ /wd4456
+ /wd4701
+ /wd4702
+ /wd4723
+
+ # CMake automatically adds exception handling flags. Remove them.
+ /GR-
+ /EHs-c-
+ # Disable exceptions in STL code.
+ -D_HAS_EXCEPTIONS=0
+ )
+
+ # This adds extra warnings for the clang-cl compiler on Windows.
+ # This is the same as the sections in the else part.
+ # These could be refactored.
+ if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
+ list(APPEND HWY_FLAGS
+ # These are not included in Wall nor Wextra:
+ -Wconversion
+ -Wsign-conversion
+ -Wvla
+ -Wnon-virtual-dtor
+
+ -Wfloat-overflow-conversion
+ -Wfloat-zero-conversion
+ -Wfor-loop-analysis
+ -Wgnu-redeclared-enum
+ -Winfinite-recursion
+ -Wself-assign
+ -Wstring-conversion
+ -Wtautological-overlap-compare
+ -Wthread-safety-analysis
+ -Wundefined-func-template
+ )
+ endif()
+
+ if (HWY_WARNINGS_ARE_ERRORS)
+ list(APPEND HWY_FLAGS /WX)
+ endif()
+else()
+ set(HWY_FLAGS
+ # Avoid changing binaries based on the current time and date.
+ -Wno-builtin-macro-redefined
+ -D__DATE__="redacted"
+ -D__TIMESTAMP__="redacted"
+ -D__TIME__="redacted"
+
+ # Optimizations
+ -fmerge-all-constants
+
+ # Warnings
+ -Wall
+ -Wextra
+ # These are not included in Wall nor Wextra:
+ -Wconversion
+ -Wsign-conversion
+ -Wvla
+ -Wnon-virtual-dtor
+ -Wcast-align # see -Wcast-align=strict on x86
+ )
+
+ if(${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
+ list(APPEND HWY_FLAGS
+ -Wfloat-overflow-conversion
+ -Wfloat-zero-conversion
+ -Wfor-loop-analysis
+ -Wgnu-redeclared-enum
+ -Winfinite-recursion
+ -Wself-assign
+ -Wstring-conversion
+ -Wtautological-overlap-compare
+ -Wthread-safety-analysis
+ -Wundefined-func-template
+
+ -fno-cxx-exceptions
+ -fno-slp-vectorize
+ -fno-vectorize
+
+ # Use color in messages
+ -fdiagnostics-show-option -fcolor-diagnostics
+ )
+ if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 6.0)
+ list(APPEND HWY_FLAGS -Wc++2a-extensions)
+ endif()
+ endif()
+
+ if (WIN32)
+ if(${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
+ list(APPEND HWY_FLAGS
+ -Wno-global-constructors
+ -Wno-language-extension-token
+ -Wno-used-but-marked-unused
+ -Wno-shadow-field-in-constructor
+ -Wno-unused-member-function
+ -Wno-unused-template
+ -Wno-c++98-compat-pedantic
+ -Wno-used-but-marked-unused
+ -Wno-zero-as-null-pointer-constant
+ )
+ endif()
+
+ list(APPEND HWY_FLAGS
+ -Wno-cast-align
+ -Wno-double-promotion
+ -Wno-float-equal
+ -Wno-format-nonliteral
+ -Wno-shadow
+ -Wno-sign-conversion
+ )
+ else()
+ list(APPEND HWY_FLAGS
+ -fmath-errno
+ -fno-exceptions
+ )
+ endif() # WIN32
+
+ # Workaround for excess precision, see #1488.
+ if (HWY_CMAKE_SSE2)
+ list(APPEND HWY_FLAGS -msse2 -mfpmath=sse)
+ endif()
+
+ # Suppress STL iterator warnings. Supported by GCC 4.4.7 and newer, which
+ # predates the C++14 (and C++17 for VQSort) we require.
+ if (${CMAKE_CXX_COMPILER_ID} MATCHES "GNU")
+ list(APPEND HWY_FLAGS -Wno-psabi)
+ endif()
+ # Clang supports this flag from 11.0.
+ if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
+ if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 11.0)
+ list(APPEND HWY_FLAGS -Wno-psabi)
+ endif()
+ endif()
+
+ if (HWY_CMAKE_ARM7)
+ list(APPEND HWY_FLAGS
+ -march=armv7-a
+ -mfpu=neon-vfpv4
+ -mfloat-abi=hard # must match the toolchain specified as CXX=
+ -DHWY_HAVE_SCALAR_F16_TYPE=0 # See #2625
+ -DHWY_NEON_HAVE_F16C=0
+ )
+ if(${CMAKE_CXX_COMPILER_ID} MATCHES "GNU")
+ # using GCC
+ list(APPEND HWY_FLAGS
+ -mfp16-format=ieee # required for vcvt_f32_f16
+ )
+ endif()
+ endif() # HWY_CMAKE_ARM7
+
+ if(HWY_RISCV)
+ # Add the gcv compiler flag so that RVV is available as a baseline target.
+ # Without this, Clang 19+ can still use RVV via runtime dispatch.
+ if(HWY_CMAKE_RVV)
+ if(RISCV_XLEN EQUAL 64)
+ list(APPEND HWY_FLAGS -march=rv64gcv1p0)
+ add_link_options(-march=rv64gcv1p0)
+ elseif(RISCV_XLEN EQUAL 32)
+ list(APPEND HWY_FLAGS -march=rv32gcv1p0)
+ add_link_options(-march=rv32gcv1p0)
+ endif()
+ if(${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
+ list(APPEND HWY_FLAGS -menable-experimental-extensions)
+ endif()
+ endif()
+ endif()
+
+ if (HWY_WARNINGS_ARE_ERRORS)
+ list(APPEND HWY_FLAGS -Werror)
+ endif()
+
+ # Prevent "wasm-ld: error: --shared-memory is disallowed by targets.cc.o
+ # because it was not compiled with 'atomics' or 'bulk-memory' features."
+ if (HWY_EMSCRIPTEN)
+ list(APPEND HWY_FLAGS -matomics)
+ endif()
+
+endif() # !MSVC
+
+if (HWY_DISABLE_FUTEX)
+ list(APPEND HWY_FLAGS -DHWY_DISABLE_FUTEX)
+endif()
+
+if (HWY_CMAKE_HEADER_ONLY)
+ list(APPEND HWY_FLAGS -DHWY_HEADER_ONLY)
+endif()
+
+include(CheckIncludeFile)
+check_include_file(sys/auxv.h HAVE_SYS_AUXV_H)
+check_include_file(asm/hwcap.h HAVE_ASM_HWCAP_H)
+
+# By default prefer STATIC build (legacy behavior)
+option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
+option(HWY_FORCE_STATIC_LIBS "Ignore BUILD_SHARED_LIBS" OFF)
+# only expose shared/static options to advanced users:
+mark_as_advanced(BUILD_SHARED_LIBS)
+mark_as_advanced(HWY_FORCE_STATIC_LIBS)
+# Define visibility settings globally:
+set(CMAKE_CXX_VISIBILITY_PRESET hidden)
+set(CMAKE_VISIBILITY_INLINES_HIDDEN 1)
+
+# Copy-cat "add_library" logic + add override.
+set(HWY_LIBRARY_TYPE "SHARED")
+if (NOT BUILD_SHARED_LIBS OR HWY_FORCE_STATIC_LIBS)
+ set(HWY_LIBRARY_TYPE "STATIC")
+endif()
+
+# This preprocessor define will drive the build, also used in the *.pc files:
+if("${HWY_LIBRARY_TYPE}" STREQUAL "SHARED")
+ set(DLLEXPORT_TO_DEFINE "HWY_SHARED_DEFINE")
+else()
+ set(DLLEXPORT_TO_DEFINE "HWY_STATIC_DEFINE")
+endif()
+
+add_library(hwy ${HWY_LIBRARY_TYPE} ${HWY_SOURCES})
+if(NOT HAVE_SYS_AUXV_H)
+ target_compile_definitions(hwy PUBLIC TOOLCHAIN_MISS_SYS_AUXV_H)
+endif()
+if(NOT HAVE_ASM_HWCAP_H)
+ target_compile_definitions(hwy PUBLIC TOOLCHAIN_MISS_ASM_HWCAP_H)
+endif()
+target_compile_definitions(hwy PUBLIC "${DLLEXPORT_TO_DEFINE}")
+target_compile_options(hwy PRIVATE ${HWY_FLAGS})
+set_property(TARGET hwy PROPERTY POSITION_INDEPENDENT_CODE ON)
+set_target_properties(hwy PROPERTIES VERSION ${LIBRARY_VERSION} SOVERSION ${LIBRARY_SOVERSION})
+target_include_directories(hwy PUBLIC
+ $
+ $)
+target_compile_features(hwy PUBLIC cxx_std_11)
+if (NOT HWY_CXX_STD_TGT_COMPILE_FEATURE STREQUAL "cxx_std_11")
+ target_compile_features(hwy PRIVATE ${HWY_CXX_STD_TGT_COMPILE_FEATURE})
+endif()
+set_target_properties(hwy PROPERTIES
+ LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/hwy/hwy.version)
+# For GCC __atomic_store_8, see #887
+target_link_libraries(hwy PRIVATE ${ATOMICS_LIBRARIES})
+# not supported by MSVC/Clang, safe to skip (we use DLLEXPORT annotations)
+if(UNIX AND NOT APPLE)
+ set_property(TARGET hwy APPEND_STRING PROPERTY
+ LINK_FLAGS " -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/hwy/hwy.version")
+endif()
+
+if (HWY_ENABLE_CONTRIB)
+add_library(hwy_contrib ${HWY_LIBRARY_TYPE} ${HWY_CONTRIB_SOURCES})
+target_link_libraries(hwy_contrib PUBLIC hwy)
+target_compile_options(hwy_contrib PRIVATE ${HWY_FLAGS} ${HWY_THREAD_FLAGS})
+set_property(TARGET hwy_contrib PROPERTY POSITION_INDEPENDENT_CODE ON)
+set_target_properties(hwy_contrib PROPERTIES VERSION ${LIBRARY_VERSION} SOVERSION ${LIBRARY_SOVERSION})
+target_include_directories(hwy_contrib PUBLIC
+ $
+ $)
+target_compile_features(hwy_contrib PUBLIC cxx_std_11)
+if (NOT HWY_CXX_STD_TGT_COMPILE_FEATURE STREQUAL "cxx_std_11")
+ target_compile_features(hwy_contrib PRIVATE ${HWY_CXX_STD_TGT_COMPILE_FEATURE})
+endif()
+set_target_properties(hwy_contrib PROPERTIES
+ LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/hwy/hwy.version)
+# For GCC __atomic_store_8, see #887
+target_link_libraries(hwy_contrib PRIVATE ${ATOMICS_LIBRARIES})
+
+# Avoid linker errors if libpthread needs to be linked
+target_link_libraries(hwy_contrib PRIVATE ${HWY_THREAD_LIBS})
+
+if (WIN32 AND NOT MSVC AND NOT HWY_DISABLE_FUTEX)
+target_link_libraries(hwy_contrib PUBLIC synchronization)
+endif()
+
+# not supported by MSVC/Clang, safe to skip (we use DLLEXPORT annotations)
+if(UNIX AND NOT APPLE)
+ set_property(TARGET hwy_contrib APPEND_STRING PROPERTY
+ LINK_FLAGS " -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/hwy/hwy.version")
+endif()
+endif() # HWY_ENABLE_CONTRIB
+
+if (HWY_ENABLE_TESTS)
+add_library(hwy_test ${HWY_LIBRARY_TYPE} ${HWY_TEST_SOURCES})
+target_link_libraries(hwy_test PUBLIC hwy)
+target_compile_options(hwy_test PRIVATE ${HWY_FLAGS})
+set_property(TARGET hwy_test PROPERTY POSITION_INDEPENDENT_CODE ON)
+set_target_properties(hwy_test PROPERTIES VERSION ${LIBRARY_VERSION} SOVERSION ${LIBRARY_SOVERSION})
+target_include_directories(hwy_test PUBLIC
+ $
+ $)
+target_compile_features(hwy_test PUBLIC cxx_std_11)
+if (NOT HWY_CXX_STD_TGT_COMPILE_FEATURE STREQUAL "cxx_std_11")
+ target_compile_features(hwy_test PRIVATE ${HWY_CXX_STD_TGT_COMPILE_FEATURE})
+endif()
+set_target_properties(hwy_test PROPERTIES
+ LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/hwy/hwy.version)
+# not supported by MSVC/Clang, safe to skip (we use DLLEXPORT annotations)
+if(UNIX AND NOT APPLE)
+ set_property(TARGET hwy_test APPEND_STRING PROPERTY
+ LINK_FLAGS " -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/hwy/hwy.version")
+endif()
+endif() # HWY_ENABLE_TESTS
+
+if (CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)
+# -------------------------------------------------------- hwy_list_targets
+# Generate a tool to print the compiled-in targets as defined by the current
+# flags. This tool will print to stderr at build time, after building hwy.
+add_executable(hwy_list_targets hwy/tests/list_targets.cc)
+target_compile_options(hwy_list_targets PRIVATE ${HWY_FLAGS})
+target_compile_features(hwy_list_targets PRIVATE ${HWY_CXX_STD_TGT_COMPILE_FEATURE})
+target_link_libraries(hwy_list_targets PRIVATE hwy)
+target_include_directories(hwy_list_targets PRIVATE
+ $)
+# TARGET_FILE always returns the path to executable
+# Naked target also not always could be run (due to the lack of '.\' prefix)
+# Thus effective command to run should contain the full path
+# and emulator prefix (if any).
+if (NOT CMAKE_CROSSCOMPILING OR CMAKE_CROSSCOMPILING_EMULATOR)
+add_custom_command(TARGET hwy_list_targets POST_BUILD
+ COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR} $ || (exit 0))
+endif()
+endif() # CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR
+
+# --------------------------------------------------------
+# Allow skipping the following sections for projects that do not need them:
+# tests, examples, benchmarks and installation.
+
+# -------------------------------------------------------- install library
+if (HWY_ENABLE_INSTALL)
+
+install(TARGETS hwy EXPORT hwy_targets
+ LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
+# Install all the headers keeping the relative path to the current directory
+# when installing them.
+foreach (source ${HWY_SOURCES})
+ if ("${source}" MATCHES "\.h$")
+ get_filename_component(dirname "${source}" DIRECTORY)
+ install(FILES "${source}"
+ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${dirname}")
+ endif()
+endforeach()
+
+if (HWY_ENABLE_CONTRIB)
+install(TARGETS hwy_contrib EXPORT hwy_targets
+ LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
+# Install all the headers keeping the relative path to the current directory
+# when installing them.
+foreach (source ${HWY_CONTRIB_SOURCES})
+ if ("${source}" MATCHES "\.h$")
+ get_filename_component(dirname "${source}" DIRECTORY)
+ install(FILES "${source}"
+ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${dirname}")
+ endif()
+endforeach()
+endif() # HWY_ENABLE_CONTRIB
+
+if (HWY_ENABLE_TESTS)
+install(TARGETS hwy_test EXPORT hwy_targets
+ LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
+# Install all the headers keeping the relative path to the current directory
+# when installing them.
+foreach (source ${HWY_TEST_SOURCES})
+ if ("${source}" MATCHES "\.h$")
+ get_filename_component(dirname "${source}" DIRECTORY)
+ install(FILES "${source}"
+ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${dirname}")
+ endif()
+endforeach()
+endif() # HWY_ENABLE_TESTS
+
+# Add a pkg-config file for libhwy and the contrib/test libraries.
+set(HWY_LIBRARY_VERSION "${CMAKE_PROJECT_VERSION}")
+set(HWY_PC_FILES libhwy.pc)
+
+if (HWY_DISABLE_FUTEX)
+ set(HWY_PC_DISABLE_FUTEX_CFLAGS "-DHWY_DISABLE_FUTEX")
+else()
+ set(HWY_PC_DISABLE_FUTEX_CFLAGS "")
+endif()
+
+if (WIN32 AND NOT MSVC AND NOT HWY_DISABLE_FUTEX)
+ set(HWY_PC_WIN32_SYNCHRONIZATION_LIBS "-lsynchronization")
+else()
+ set(HWY_PC_WIN32_SYNCHRONIZATION_LIBS "")
+endif()
+
+if (HWY_ENABLE_CONTRIB)
+list(APPEND HWY_PC_FILES libhwy-contrib.pc)
+endif() # HWY_ENABLE_CONTRIB
+if (HWY_ENABLE_TESTS)
+
+if (HWY_TEST_STANDALONE)
+ set(HWY_PC_HWY_TEST_REQUIRES "")
+ set(HWY_PC_HWY_TEST_CFLAGS "-DHWY_TEST_STANDALONE=1")
+else()
+ set(HWY_PC_HWY_TEST_REQUIRES "gtest")
+ set(HWY_PC_HWY_TEST_CFLAGS "")
+endif()
+
+list(APPEND HWY_PC_FILES libhwy-test.pc)
+endif() # HWY_ENABLE_TESTS
+foreach (pc ${HWY_PC_FILES})
+ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/${pc}.in" "${pc}" @ONLY)
+ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${pc}"
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
+endforeach()
+
+endif() # HWY_ENABLE_INSTALL
+# -------------------------------------------------------- Examples
+if (HWY_ENABLE_EXAMPLES)
+
+# Avoids mismatch between GTest's static CRT and our dynamic.
+set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
+
+# Programming exercise with integrated benchmark
+add_executable(hwy_benchmark hwy/examples/benchmark.cc)
+target_sources(hwy_benchmark PRIVATE
+ hwy/nanobenchmark.h)
+# Try adding one of -DHWY_COMPILE_ONLY_SCALAR, -DHWY_COMPILE_ONLY_EMU128 or
+# -DHWY_COMPILE_ONLY_STATIC to observe the difference in targets printed.
+target_compile_options(hwy_benchmark PRIVATE ${HWY_FLAGS})
+target_compile_features(hwy_benchmark PRIVATE ${HWY_CXX_STD_TGT_COMPILE_FEATURE})
+target_link_libraries(hwy_benchmark PRIVATE hwy)
+target_link_libraries(hwy_benchmark PRIVATE ${ATOMICS_LIBRARIES})
+set_target_properties(hwy_benchmark
+ PROPERTIES RUNTIME_OUTPUT_DIRECTORY "examples/")
+
+# Profiler demo
+if (HWY_ENABLE_CONTRIB)
+add_executable(hwy_profiler_example hwy/examples/profiler_example.cc)
+target_sources(hwy_profiler_example PRIVATE
+ hwy/profiler.h)
+target_compile_options(hwy_profiler_example PRIVATE ${HWY_FLAGS} ${HWY_THREAD_FLAGS})
+target_compile_features(hwy_profiler_example PRIVATE ${HWY_CXX_STD_TGT_COMPILE_FEATURE})
+target_link_libraries(hwy_profiler_example PRIVATE hwy hwy_contrib)
+target_link_libraries(hwy_profiler_example PRIVATE ${ATOMICS_LIBRARIES})
+target_link_libraries(hwy_profiler_example PRIVATE ${HWY_THREAD_LIBS})
+set_target_properties(hwy_profiler_example
+ PROPERTIES RUNTIME_OUTPUT_DIRECTORY "examples/")
+endif() # HWY_ENABLE_CONTRIB
+
+# Simple array sum example
+add_executable(sum_array_simple hwy/examples/sum_array_simple.cc)
+target_compile_options(sum_array_simple PRIVATE ${HWY_FLAGS})
+target_compile_features(sum_array_simple PRIVATE ${HWY_CXX_STD_TGT_COMPILE_FEATURE})
+target_link_libraries(sum_array_simple PRIVATE hwy)
+target_link_libraries(sum_array_simple PRIVATE ${ATOMICS_LIBRARIES})
+set_target_properties(sum_array_simple
+ PROPERTIES RUNTIME_OUTPUT_DIRECTORY "examples/")
+
+# Advanced array sum example
+add_executable(sum_array_advanced hwy/examples/sum_array_advanced.cc)
+target_compile_options(sum_array_advanced PRIVATE ${HWY_FLAGS})
+target_compile_features(sum_array_advanced PRIVATE ${HWY_CXX_STD_TGT_COMPILE_FEATURE})
+target_link_libraries(sum_array_advanced PRIVATE hwy)
+target_link_libraries(sum_array_advanced PRIVATE ${ATOMICS_LIBRARIES})
+set_target_properties(sum_array_advanced
+ PROPERTIES RUNTIME_OUTPUT_DIRECTORY "examples/")
+
+endif() # HWY_ENABLE_EXAMPLES
+# -------------------------------------------------------- Tests
+
+if(HWY_ENABLE_TESTS)
+include(CTest)
+
+if(BUILD_TESTING)
+enable_testing()
+include(GoogleTest)
+
+set(HWY_SYSTEM_GTEST OFF CACHE BOOL "Use pre-installed googletest?")
+
+if(NOT HWY_TEST_STANDALONE)
+if(HWY_SYSTEM_GTEST)
+find_package(GTest REQUIRED)
+else()
+# Download and unpack googletest at configure time
+configure_file(CMakeLists.txt.in googletest-download/CMakeLists.txt)
+execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
+ RESULT_VARIABLE result
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download )
+if(result)
+ message(FATAL_ERROR "CMake step for googletest failed: ${result}")
+endif()
+execute_process(COMMAND ${CMAKE_COMMAND} --build .
+ RESULT_VARIABLE result
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download )
+if(result)
+ message(FATAL_ERROR "Build step for googletest failed: ${result}")
+endif()
+
+# Prevent overriding the parent project's compiler/linker
+# settings on Windows
+set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
+
+# Add googletest directly to our build. This defines
+# the gtest and gtest_main targets.
+add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/googletest-src
+ ${CMAKE_CURRENT_BINARY_DIR}/googletest-build
+ EXCLUDE_FROM_ALL)
+endif() # HWY_SYSTEM_GTEST
+endif() # HWY_TEST_STANDALONE
+
+set(HWY_TEST_FILES
+ hwy/abort_test.cc
+ hwy/aligned_allocator_test.cc
+ hwy/base_test.cc
+ hwy/bit_set_test.cc
+ hwy/highway_test.cc
+ hwy/nanobenchmark_test.cc
+ hwy/perf_counters_test.cc
+ hwy/targets_test.cc
+ hwy/examples/skeleton_test.cc
+ hwy/tests/arithmetic_test.cc
+ hwy/tests/bit_permute_test.cc
+ hwy/tests/blockwise_combine_test.cc
+ hwy/tests/blockwise_shift_test.cc
+ hwy/tests/blockwise_test.cc
+ hwy/tests/cast_test.cc
+ hwy/tests/combine_test.cc
+ hwy/tests/compare_128_test.cc
+ hwy/tests/compare_test.cc
+ hwy/tests/complex_arithmetic_test.cc
+ hwy/tests/compress_test.cc
+ hwy/tests/concat_test.cc
+ hwy/tests/convert_test.cc
+ hwy/tests/count_test.cc
+ hwy/tests/crypto_test.cc
+ hwy/tests/demote_test.cc
+ hwy/tests/div_test.cc
+ hwy/tests/dup128_vec_test.cc
+ hwy/tests/expand_test.cc
+ hwy/tests/float_test.cc
+ hwy/tests/fma_test.cc
+ hwy/tests/foreach_vec_test.cc
+ hwy/tests/if_test.cc
+ hwy/tests/in_range_float_to_int_conv_test.cc
+ hwy/tests/interleaved_test.cc
+ hwy/tests/logical_test.cc
+ hwy/tests/mask_combine_test.cc
+ hwy/tests/mask_convert_test.cc
+ hwy/tests/mask_mem_test.cc
+ hwy/tests/mask_set_test.cc
+ hwy/tests/mask_slide_test.cc
+ hwy/tests/mask_test.cc
+ hwy/tests/masked_arithmetic_test.cc
+ hwy/tests/masked_compare_test.cc
+ hwy/tests/masked_minmax_test.cc
+ hwy/tests/memory_test.cc
+ hwy/tests/minmax_magnitude_test.cc
+ hwy/tests/minmax_number_test.cc
+ hwy/tests/minmax_test.cc
+ hwy/tests/minmax128_test.cc
+ hwy/tests/mul_by_pow2_test.cc
+ hwy/tests/mul_pairwise_test.cc
+ hwy/tests/mul_test.cc
+ hwy/tests/neg_test.cc
+ hwy/tests/reduction_test.cc
+ hwy/tests/resize_test.cc
+ hwy/tests/reverse_test.cc
+ hwy/tests/rotate_test.cc
+ hwy/tests/saturated_test.cc
+ hwy/tests/shift_test.cc
+ hwy/tests/shuffle4_test.cc
+ hwy/tests/sign_test.cc
+ hwy/tests/slide_up_down_test.cc
+ hwy/tests/sums_abs_diff_test.cc
+ hwy/tests/swizzle_block_test.cc
+ hwy/tests/swizzle_test.cc
+ hwy/tests/table_test.cc
+ hwy/tests/test_util_test.cc
+ hwy/tests/truncate_test.cc
+ hwy/tests/tuple_test.cc
+ hwy/tests/widen_mul_test.cc
+)
+
+set(HWY_TEST_LIBS hwy hwy_test)
+
+if (HWY_ENABLE_CONTRIB)
+list(APPEND HWY_TEST_LIBS hwy_contrib)
+
+list(APPEND HWY_TEST_FILES
+ hwy/auto_tune_test.cc
+ hwy/contrib/algo/copy_test.cc
+ hwy/contrib/algo/count_value_test.cc
+ hwy/contrib/algo/find_test.cc
+ hwy/contrib/algo/minmax_value_test.cc
+ hwy/contrib/algo/transform_test.cc
+ hwy/contrib/bit_pack/bit_pack_test.cc
+ hwy/contrib/dot/dot_test.cc
+ hwy/contrib/matvec/matvec_test.cc
+ hwy/contrib/image/image_test.cc
+ # Disabled due to SIGILL in clang7 debug build during gtest discovery phase,
+ # not reproducible locally. Still tested via bazel build.
+ hwy/contrib/math/math_test.cc
+ hwy/contrib/math/math_hyper_test.cc
+ hwy/contrib/math/math_tan_test.cc
+ hwy/contrib/math/math_trig_test.cc
+ hwy/contrib/random/random_test.cc
+ hwy/contrib/sort/bench_sort.cc
+ hwy/contrib/sort/sort_test.cc
+ hwy/contrib/sort/sort_unit_test.cc
+ hwy/contrib/thread_pool/spin_test.cc
+ hwy/contrib/thread_pool/thread_pool_test.cc
+ hwy/contrib/thread_pool/topology_test.cc
+ hwy/contrib/unroller/unroller_test.cc
+)
+endif() # HWY_ENABLE_CONTRIB
+
+if(HWY_TEST_STANDALONE)
+ set(HWY_GTEST_LIBS "")
+else()
+ if(HWY_SYSTEM_GTEST)
+ if (CMAKE_VERSION VERSION_LESS 3.20)
+ set(HWY_GTEST_LIBS GTest::GTest GTest::Main)
+ else()
+ set(HWY_GTEST_LIBS GTest::gtest GTest::gtest_main)
+ endif()
+ else()
+ set(HWY_GTEST_LIBS gtest gtest_main)
+ endif()
+endif() # HWY_TEST_STANDALONE
+
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/tests)
+foreach (TESTFILE IN LISTS HWY_TEST_FILES)
+ # The TESTNAME is the name without the extension or directory.
+ get_filename_component(TESTNAME ${TESTFILE} NAME_WE)
+ add_executable(${TESTNAME} ${TESTFILE})
+ target_compile_options(${TESTNAME} PRIVATE ${HWY_FLAGS} ${HWY_THREAD_FLAGS})
+ # Test all targets, not just the best/baseline. This changes the default
+ # policy to all-attainable; note that setting -DHWY_COMPILE_* directly can
+ # cause compile errors because only one may be set, and other CMakeLists.txt
+ # that include us may set them.
+ target_compile_options(${TESTNAME} PRIVATE -DHWY_IS_TEST=1)
+ if(HWY_TEST_STANDALONE)
+ target_compile_options(${TESTNAME} PRIVATE -DHWY_TEST_STANDALONE=1)
+ endif()
+ target_compile_features(${TESTNAME} PRIVATE ${HWY_CXX_STD_TGT_COMPILE_FEATURE})
+
+ target_link_libraries(${TESTNAME} PRIVATE ${HWY_TEST_LIBS} ${HWY_GTEST_LIBS})
+ # For GCC __atomic_store_8, see #887
+ target_link_libraries(${TESTNAME} PRIVATE ${ATOMICS_LIBRARIES})
+
+ # Avoid linker errors if libpthread needs to be linked
+ target_link_libraries(${TESTNAME} PRIVATE ${HWY_THREAD_LIBS})
+
+ # Output test targets in the test directory.
+ set_target_properties(${TESTNAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "tests")
+
+ if (HWY_EMSCRIPTEN)
+ set_target_properties(${TESTNAME} PROPERTIES LINK_FLAGS "-s SINGLE_FILE=1")
+ endif()
+
+ if(${CMAKE_VERSION} VERSION_LESS "3.10.3")
+ gtest_discover_tests(${TESTNAME} TIMEOUT 60)
+ else ()
+ gtest_discover_tests(${TESTNAME} DISCOVERY_TIMEOUT 60)
+ endif ()
+endforeach ()
+
+# The skeleton test uses the skeleton library code.
+target_sources(skeleton_test PRIVATE hwy/examples/skeleton.cc)
+target_compile_definitions(skeleton_test PRIVATE hwy_EXPORTS)
+
+endif() # BUILD_TESTING
+endif() # HWY_ENABLE_TESTS
+
+if (HWY_ENABLE_INSTALL)
+ # write hwy-config file to handle `Config` mode
+ include(CMakePackageConfigHelpers)
+ write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/hwy-config-version.cmake" COMPATIBILITY SameMajorVersion)
+ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/hwy-config-version.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/hwy")
+ install(EXPORT hwy_targets NAMESPACE "${namespace}" FILE hwy-config.cmake DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/hwy")
+endif()
diff --git a/lib/highway/LICENSE b/lib/highway/LICENSE
new file mode 100644
index 00000000000..1af4f15ca70
--- /dev/null
+++ b/lib/highway/LICENSE
@@ -0,0 +1,371 @@
+This project is primarily dual-licensed under your choice of either the Apache
+License 2.0 or the BSD 3-Clause License.
+
+The following files are licensed under different terms:
+* hwy/contrib/random/random-inl.h: CC0 1.0 Universal
+
+The full texts of all applicable licenses are included below, separated by
+'---'.
+
+--------------------------------------------------------------------------------
+Apache License 2.0
+--------------------------------------------------------------------------------
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
+
+--------------------------------------------------------------------------------
+BSD 3-Clause License
+--------------------------------------------------------------------------------
+
+Copyright (c) The Highway Project Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+--------------------------------------------------------------------------------
+CC0 1.0 Universal
+--------------------------------------------------------------------------------
+
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+ HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display,
+ communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+ likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+ v. rights protecting the extraction, dissemination, use and reuse of data
+ in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation
+ thereof, including any amended or successor version of such
+ directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+ world based on applicable law or treaty, and any national
+ implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+ warranties of any kind concerning the Work, express, implied,
+ statutory or otherwise, including without limitation warranties of
+ title, merchantability, fitness for a particular purpose, non
+ infringement, or the absence of latent or other defects, accuracy, or
+ the present or absence of errors, whether or not discoverable, all to
+ the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person's Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the
+ Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to
+ this CC0 or use of the Work.
\ No newline at end of file
diff --git a/lib/highway/README.md b/lib/highway/README.md
new file mode 100644
index 00000000000..ba78a9de854
--- /dev/null
+++ b/lib/highway/README.md
@@ -0,0 +1,521 @@
+# Efficient and performance-portable vector software
+
+[//]: # (placeholder, do not remove)
+
+Highway is a C++ library that provides portable SIMD/vector intrinsics.
+
+[Documentation](https://google.github.io/highway/en/master/)
+
+Previously licensed under Apache 2, now dual-licensed as Apache 2 / BSD-3.
+
+## Why
+
+We are passionate about high-performance software. We see major untapped
+potential in CPUs (servers, mobile, desktops). Highway is for engineers who want
+to reliably and economically push the boundaries of what is possible in
+software.
+
+## How
+
+CPUs provide SIMD/vector instructions that apply the same operation to multiple
+data items. This can reduce energy usage e.g. *fivefold* because fewer
+instructions are executed. We also often see *5-10x* speedups.
+
+Highway makes SIMD/vector programming practical and workable according to these
+guiding principles:
+
+**Does what you expect**: Highway is a C++ library with carefully-chosen
+functions that map well to CPU instructions without extensive compiler
+transformations. The resulting code is more predictable and robust to code
+changes/compiler updates than autovectorization.
+
+**Works on widely-used platforms**: Highway supports seven architectures; the
+same application code can target various instruction sets, including those with
+'scalable' vectors (size unknown at compile time). Highway only requires C++17
+(language features, not necessarily the library) and supports four families of
+compilers. If you want to use Highway on other platforms, please raise an issue.
+
+**Flexible to deploy**: Applications using Highway can run on heterogeneous
+clouds or client devices, choosing the best available instruction set at
+runtime. Alternatively, developers may choose to target a single instruction set
+without any runtime overhead. In both cases, the application code is the same
+except for swapping `HWY_STATIC_DISPATCH` with `HWY_DYNAMIC_DISPATCH` plus one
+line of code. See also @kfjahnke's
+[introduction to dispatching](https://github.com/kfjahnke/zimt/blob/main/examples/multi_isa_example/multi_simd_isa.md).
+
+**Suitable for a variety of domains**: Highway provides an extensive set of
+operations, used for image processing (floating-point), compression, video
+analysis, linear algebra, cryptography, sorting and random generation. We
+recognise that new use-cases may require additional ops and are happy to add
+them where it makes sense (e.g. no performance cliffs on some architectures). If
+you would like to discuss, please file an issue.
+
+**Rewards data-parallel design**: Highway provides tools such as Gather,
+MaskedLoad, and FixedTag to enable speedups for legacy data structures. However,
+the biggest gains are unlocked by designing algorithms and data structures for
+scalable vectors. Helpful techniques include batching, structure-of-array
+layouts, and aligned/padded allocations.
+
+We recommend these resources for getting started:
+
+- [SIMD programming with Highway talk](https://www.youtube.com/watch?v=R57biOOhnJM)
+- [SIMD for C++ Developers](http://const.me/articles/simd/simd.pdf)
+- [Algorithms for Modern Hardware](https://en.algorithmica.org/hpc/)
+- [Optimizing software in C++](https://agner.org/optimize/optimizing_cpp.pdf)
+- [Improving performance with SIMD intrinsics in three use cases](https://stackoverflow.blog/2020/07/08/improving-performance-with-simd-intrinsics-in-three-use-cases/)
+
+## Examples
+
+Online demos using Compiler Explorer:
+
+- [multiple targets with dynamic dispatch](https://gcc.godbolt.org/z/KM3ben7ET)
+ (more complicated, but flexible and uses best available SIMD)
+- [single target using -m flags](https://gcc.godbolt.org/z/rGnjMevKG)
+ (simpler, but requires/only uses the instruction set enabled by compiler
+ flags)
+
+We observe that Highway is referenced in the following open source projects,
+found via sourcegraph.com. Most are GitHub repositories. If you would like to
+add your project or link to it directly, feel free to raise an issue or contact
+us via the below email.
+
+* Audio: [Zimtohrli perceptual metric](https://github.com/google/zimtohrli)
+* Browsers: Chromium (+Vivaldi), Firefox (+floorp / foxhound / librewolf /
+ Waterfox)
+* Computational biology: [RNA analysis](https://github.com/bnprks/BPCells),
+ [long-sequence preprocessing](https://github.com/OpenGene/fastplong)
+* Computer graphics: ghostty-org/ghostty,
+ [Sparse voxel renderer](https://github.com/rools/voxl),
+ [tgfx 2D Graphics library](https://github.com/Tencent/tgfx)
+* Cryptography: google/distributed_point_functions, google/shell-encryption
+* Data structures: bkille/BitLib
+* Image codecs: eustas/2im,
+ [Grok JPEG 2000](https://github.com/GrokImageCompression/grok),
+ [JPEG XL](https://github.com/libjxl/libjxl),
+ [JPEGenc](https://github.com/osamu620/JPEGenc),
+ [Jpegli](https://github.com/google/jpegli),
+ [libaom](https://aomedia.googlesource.com/aom/),
+ [OpenHTJ2K](https://github.com/osamu620/OpenHTJ2K)
+* Image processing: awxkee/aire, cloudinary/ssimulacra2,
+ [libvips](https://github.com/libvips/libvips), m-ab-s/media-autobuild_suite,
+* Image viewers: AlienCowEatCake/ImageViewer, diffractor/diffractor,
+ [Lux panorama/image viewer](https://bitbucket.org/kfj/pv/),
+ mirillis/jpegxl-wic
+* Information retrieval:
+ [iresearch database index](https://github.com/iresearch-toolkit/iresearch),
+ michaeljclark/zvec,
+ [nebula interactive analytics / OLAP](https://github.com/varchar-io/nebula),
+ [`ScaNN` Scalable Nearest Neighbors](https://github.com/google-research/google-research/tree/7a269cb2ce0ae1db591fe11b62cbc0be7d72532a/scann),
+* Machine learning: array2d/deepx,
+ [gemma.cpp](https://github.com/google/gemma.cpp), Tensorflow, Numpy,
+ zpye/SimpleInfer
+* Programming languages:
+ [AOT-compiled python](https://github.com/exaloop/codon), oven-sh/bun, V8/V8,
+ yinqiwen/rapidudf
+* Robotics:
+ [MIT Model-Based Design and Verification](https://github.com/RobotLocomotion/drake)
+* Vector search: 1yefuwang1/vectorlite, vespa-engine/vespa
+
+Other
+
+* [Evaluation of C++ SIMD Libraries](https://www.mnm-team.org/pub/Fopras/rock23/):
+ "Highway excelled with a strong performance across multiple SIMD extensions
+ [..]. Thus, Highway may currently be the most suitable SIMD library for many
+ software projects."
+* [zimt](https://github.com/kfjahnke/zimt): C++11 template library to process n-dimensional arrays with multi-threaded SIMD code
+* [vectorized Quicksort](https://github.com/google/highway/tree/master/hwy/contrib/sort) ([paper](https://arxiv.org/abs/2205.05982))
+
+If you'd like to get Highway, in addition to cloning from this GitHub repository
+or using it as a Git submodule, you can also find it in the following package
+managers or repositories:
+
+* alpinelinux
+* conan-io
+* conda-forge
+* DragonFlyBSD,
+* fd00/yacp
+* freebsd
+* getsolus/packages
+* ghostbsd
+* microsoft/vcpkg
+* MidnightBSD
+* MSYS2
+* NetBSD
+* openSUSE
+* opnsense
+* Xilinx/Vitis_Libraries
+* xmake-io/xmake-repo
+
+See also the list at https://repology.org/project/highway-simd-library/versions
+.
+
+## Current status
+
+### Targets
+
+Highway supports 27 targets, listed in alphabetical order of platform:
+
+- Any: `EMU128`, `SCALAR`;
+- Armv7+: `NEON_WITHOUT_AES`, `NEON`, `NEON_BF16`, `SVE`, `SVE2`, `SVE_256`,
+ `SVE2_128`;
+- IBM Z: `Z14`, `Z15`;
+- LoongArch: `LSX`, `LASX`;
+- POWER: `PPC8` (v2.07), `PPC9` (v3.0), `PPC10` (v3.1B, not yet supported due
+ to compiler bugs, see #1207; also requires QEMU 7.2);
+- RISC-V: `RVV` (1.0);
+- WebAssembly: `WASM`, `WASM_EMU256` (a 2x unrolled version of wasm128,
+ enabled if `HWY_WANT_WASM2` is defined. This will remain supported until it
+ is potentially superseded by a future version of WASM.);
+- x86:
+ - `SSE2`
+ - `SSSE3` (~Intel Core)
+ - `SSE4` (~Nehalem, also includes AES + CLMUL).
+ - `AVX2` (~Haswell, also includes BMI2 + F16 + FMA)
+ - `AVX3` (~Skylake, AVX-512F/BW/CD/DQ/VL)
+ - `AVX3_DL` (~Icelake, includes `BitAlg` + `CLMUL` + `GFNI` + `VAES` +
+ `VBMI` + `VBMI2` + `VNNI` + `VPOPCNT`),
+ - `AVX3_ZEN4` (AVX3_DL plus BF16, optimized for AMD Zen4; requires opt-in
+ by defining `HWY_WANT_AVX3_ZEN4` if compiling for static dispatch, but
+ enabled by default for runtime dispatch),
+ - `AVX3_SPR` (~Sapphire Rapids, includes AVX-512FP16)
+ - `AVX10_2` (~Diamond Rapids)
+
+Our policy is that unless otherwise specified, targets will remain supported as
+long as they can be (cross-)compiled with currently supported Clang or GCC, and
+tested using QEMU. If the target can be compiled with LLVM trunk and tested
+using our version of QEMU without extra flags, then it is eligible for inclusion
+in our continuous testing infrastructure. Otherwise, the target will be manually
+tested before releases with selected versions/configurations of Clang and GCC.
+
+SVE was initially tested using farm_sve (see acknowledgments).
+
+### Versioning
+
+Highway releases aim to follow the semver.org system (MAJOR.MINOR.PATCH),
+incrementing MINOR after backward-compatible additions and PATCH after
+backward-compatible fixes. We recommend using releases (rather than the Git tip)
+because they are tested more extensively, see below.
+
+The current version 1.0 signals an increased focus on backwards compatibility.
+Applications using documented functionality will remain compatible with future
+updates that have the same major version number.
+
+### Testing
+
+Continuous integration tests build with a recent version of Clang (running on
+native x86, or QEMU for RISC-V and Arm) and MSVC 2019 (v19.28, running on native
+x86).
+
+Before releases, we also test on x86 with Clang and GCC, and Armv7/8 via GCC
+cross-compile. See the [testing process](g3doc/release_testing_process.md) for
+details.
+
+### Related modules
+
+The `contrib` directory contains SIMD-related utilities: an image class with
+aligned rows, a math library (16 functions already implemented, mostly
+trigonometry), and functions for computing dot products and sorting.
+
+### Other libraries
+
+If you only require x86 support, you may also use Agner Fog's
+[VCL vector class library](https://github.com/vectorclass). It includes many
+functions including a complete math library.
+
+If you have existing code using x86/NEON intrinsics, you may be interested in
+[SIMDe](https://github.com/simd-everywhere/simde), which emulates those
+intrinsics using other platforms' intrinsics or autovectorization.
+
+[xSIMD](https://github.com/xtensor-stack/xsimd) is a header only C++ library.
+It supports Arm, Power, RISC-V, WebAssembly and x86 targets. Has a high level
+interface, but fewer supported operations.
+
+[NumKong](https://github.com/ashvardanian/NumKong) a SIMD accelerated math C
+library focused on operations such as dot products and mixed precision matrix
+multiplications. It can be used from C++, Go, Python, Rust, Swift and
+WebAssembly. Accelerated operations are availble on ARM, LoongArch, Power,
+RISC-V and x86.
+
+## Installation
+
+This project uses CMake to generate and build. In a Debian-based system you can
+install it via:
+
+```bash
+sudo apt install cmake
+```
+
+Highway's unit tests use [googletest](https://github.com/google/googletest).
+By default, Highway's CMake downloads this dependency at configuration time.
+You can avoid this by setting the `HWY_SYSTEM_GTEST` CMake variable to ON and
+installing gtest separately:
+
+```bash
+sudo apt install libgtest-dev
+```
+
+Alternatively, you can define `HWY_TEST_STANDALONE=1` and remove all occurrences
+of `gtest_main` in each BUILD file, then tests avoid the dependency on GUnit.
+
+Running cross-compiled tests requires support from the OS, which on Debian is
+provided by the `qemu-user-binfmt` package.
+
+To build Highway as a shared or static library (depending on BUILD_SHARED_LIBS),
+the standard CMake workflow can be used:
+
+```bash
+mkdir -p build && cd build
+cmake ..
+make -j && make test
+```
+
+Or you can run `run_tests.sh` (`run_tests.bat` on Windows).
+
+Bazel is also supported for building, but it is not as widely used/tested.
+
+When building for Armv7, a limitation of current compilers requires you to add
+`-DHWY_CMAKE_ARM7:BOOL=ON` to the CMake command line; see #834 and #1032. We
+understand that work is underway to remove this limitation.
+
+To benefit from Armv8/v9 vusdot and vusdotq instructions, you can add "+i8mm" to
+the -march compiler flag, assuming the target CPU(s) support that.
+
+Building on 32-bit x86 is not officially supported, and AVX2/3 are disabled by
+default there. Note that johnplatts has successfully built and run the Highway
+tests on 32-bit x86, including AVX2/3, on GCC 7/8 and Clang 8/11/12. On Ubuntu
+22.04, Clang 11 and 12, but not later versions, require extra compiler flags
+`-m32 -isystem /usr/i686-linux-gnu/include`. Clang 10 and earlier require the
+above plus `-isystem /usr/i686-linux-gnu/include/c++/12/i686-linux-gnu`. See
+[#1279](https://github.com/google/highway/issues/1279).
+
+## Building highway - Using vcpkg
+
+highway is now available in [vcpkg](https://github.com/Microsoft/vcpkg)
+
+```bash
+vcpkg install highway
+```
+
+The highway port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository.
+
+## Quick start
+
+You can use the `benchmark` inside examples/ as a starting point.
+
+A [quick-reference page](g3doc/quick_reference.md) briefly lists all operations
+and their parameters, and the [instruction_matrix](g3doc/instruction_matrix.pdf)
+indicates the number of instructions per operation.
+
+The [FAQ](g3doc/faq.md) answers questions about portability, API design and
+where to find more information.
+
+We recommend using full SIMD vectors whenever possible for maximum performance
+portability. To obtain them, pass a `ScalableTag` (or equivalently
+`HWY_FULL(float)`) tag to functions such as `Zero/Set/Load`. There are two
+alternatives for use-cases requiring an upper bound on the lanes:
+
+- For up to `N` lanes, specify `CappedTag` or the equivalent
+ `HWY_CAPPED(T, N)`. The actual number of lanes will be `N` rounded down to
+ the nearest power of two, such as 4 if `N` is 5, or 8 if `N` is 8. This is
+ useful for data structures such as a narrow matrix. A loop is still required
+ because vectors may actually have fewer than `N` lanes.
+
+- For exactly a power of two `N` lanes, specify `FixedTag`. The largest
+ supported `N` depends on the target, but is guaranteed to be at least
+ `16/sizeof(T)`.
+
+Due to ADL restrictions, user code calling Highway ops must either:
+
+* Reside inside `namespace hwy { namespace HWY_NAMESPACE {`; or
+* prefix each op with an alias such as `namespace hn = hwy::HWY_NAMESPACE;
+ hn::Add()`; or
+* add using-declarations for each op used: `using hwy::HWY_NAMESPACE::Add;`.
+
+Additionally, each function that calls Highway ops (such as `Load`) must either
+be prefixed with `HWY_ATTR`, OR reside between `HWY_BEFORE_NAMESPACE()` and
+`HWY_AFTER_NAMESPACE()`. Lambda functions currently require `HWY_ATTR` before
+their opening brace.
+
+Do not use namespace-scope nor `static` initializers for SIMD vectors because
+this can cause SIGILL when using runtime dispatch and the compiler chooses an
+initializer compiled for a target not supported by the current CPU. Instead,
+constants initialized via `Set` should generally be local (const) variables.
+
+The entry points into code using Highway differ slightly depending on whether
+they use static or dynamic dispatch. In both cases, we recommend that the
+top-level function receives one or more pointers to arrays, rather than
+target-specific vector types.
+
+* For static dispatch, `HWY_TARGET` will be the best available target among
+ `HWY_BASELINE_TARGETS`, i.e. those allowed for use by the compiler (see
+ [quick-reference](g3doc/quick_reference.md)). Functions inside
+ `HWY_NAMESPACE` can be called using `HWY_STATIC_DISPATCH(func)(args)` within
+ the same module they are defined in. You can call the function from other
+ modules by wrapping it in a regular function and declaring the regular
+ function in a header.
+
+* For dynamic dispatch, a table of function pointers is generated via the
+ `HWY_EXPORT` macro that is used by `HWY_DYNAMIC_DISPATCH(func)(args)` to
+ call the best function pointer for the current CPU's supported targets. A
+ module is automatically compiled for each target in `HWY_TARGETS` (see
+ [quick-reference](g3doc/quick_reference.md)) if `HWY_TARGET_INCLUDE` is
+ defined and `foreach_target.h` is included. Note that the first invocation
+ of `HWY_DYNAMIC_DISPATCH`, or each call to the pointer returned by the first
+ invocation of `HWY_DYNAMIC_POINTER`, involves some CPU detection overhead.
+ You can prevent this by calling the following before any invocation of
+ `HWY_DYNAMIC_*`: `hwy::GetChosenTarget().Update(hwy::SupportedTargets());`.
+
+See also a separate
+[introduction to dynamic dispatch](https://github.com/kfjahnke/zimt/blob/multi_isa/examples/multi_isa_example/multi_simd_isa.md)
+by @kfjahnke.
+
+When using dynamic dispatch, `foreach_target.h` is included from translation
+units (.cc files), not headers. Headers containing vector code shared between
+several translation units require a special include guard, for example the
+following taken from `examples/skeleton-inl.h`:
+
+```
+#if defined(HIGHWAY_HWY_EXAMPLES_SKELETON_INL_H_) == defined(HWY_TARGET_TOGGLE)
+#ifdef HIGHWAY_HWY_EXAMPLES_SKELETON_INL_H_
+#undef HIGHWAY_HWY_EXAMPLES_SKELETON_INL_H_
+#else
+#define HIGHWAY_HWY_EXAMPLES_SKELETON_INL_H_
+#endif
+
+#include "hwy/highway.h"
+// Your vector code
+#endif
+```
+
+By convention, we name such headers `-inl.h` because their contents (often
+function templates) are usually inlined.
+
+## Compiler flags
+
+Applications should be compiled with optimizations enabled. Without inlining
+SIMD code may slow down by factors of 10 to 100. For clang and GCC, `-O2` is
+generally sufficient.
+
+For MSVC, we recommend compiling with `/Gv` to allow non-inlined functions to
+pass vector arguments in registers. If intending to use the AVX2 target together
+with half-width vectors (e.g. for `PromoteTo`), it is also important to compile
+with `/arch:AVX2`. This seems to be the only way to reliably generate
+VEX-encoded SSE instructions on MSVC. Sometimes MSVC generates VEX-encoded SSE
+instructions, if they are mixed with AVX, but not always, see
+[DevCom-10618264](https://developercommunity.visualstudio.com/t/10618264).
+Otherwise, mixing VEX-encoded AVX2 instructions and non-VEX SSE may cause severe
+performance degradation. Unfortunately, with `/arch:AVX2` option, the resulting
+binary will then require AVX2. Note that no such flag is needed for clang and
+GCC because they support target-specific attributes, which we use to ensure
+proper VEX code generation for AVX2 targets.
+
+## Strip-mining loops
+
+When vectorizing a loop, an important question is whether and how to deal with
+a number of iterations ('trip count', denoted `count`) that does not evenly
+divide the vector size `N = Lanes(d)`. For example, it may be necessary to avoid
+writing past the end of an array.
+
+In this section, let `T` denote the element type and `d = ScalableTag`.
+Assume the loop body is given as a function `template
+void LoopBody(D d, size_t index, size_t max_n)`.
+
+"Strip-mining" is a technique for vectorizing a loop by transforming it into an
+outer loop and inner loop, such that the number of iterations in the inner loop
+matches the vector width. Then, the inner loop is replaced with vector
+operations.
+
+Highway offers several strategies for loop vectorization:
+
+* Ensure all inputs/outputs are padded. Then the (outer) loop is simply
+
+ ```
+ for (size_t i = 0; i < count; i += N) LoopBody(d, i, 0);
+ ```
+ Here, the template parameter and second function argument are not needed.
+
+ This is the preferred option, unless `N` is in the thousands and vector
+ operations are pipelined with long latencies. This was the case for
+ supercomputers in the 90s, but nowadays ALUs are cheap and we see most
+ implementations split vectors into 1, 2 or 4 parts, so there is little cost
+ to processing entire vectors even if we do not need all their lanes. Indeed
+ this avoids the (potentially large) cost of predication or partial
+ loads/stores on older targets, and does not duplicate code.
+
+* Process whole vectors and include previously processed elements
+ in the last vector:
+ ```
+ for (size_t i = 0; i < count; i += N) LoopBody(d, HWY_MIN(i, count - N), 0);
+ ```
+
+ This is the second preferred option provided that `count >= N`
+ and `LoopBody` is idempotent. Some elements might be processed twice, but
+ a single code path and full vectorization is usually worth it. Even if
+ `count < N`, it usually makes sense to pad inputs/outputs up to `N`.
+
+* Use the `Transform*` functions in hwy/contrib/algo/transform-inl.h. This
+ takes care of the loop and remainder handling and you simply define a
+ generic lambda function (C++14) or functor which receives the current vector
+ from the input/output array, plus optionally vectors from up to two extra
+ input arrays, and returns the value to write to the input/output array.
+
+ Here is an example implementing the BLAS function SAXPY (`alpha * x + y`):
+
+ ```
+ Transform1(d, x, n, y, [](auto d, const auto v, const auto v1) HWY_ATTR {
+ return MulAdd(Set(d, alpha), v, v1);
+ });
+ ```
+
+* Process whole vectors as above, followed by a scalar loop:
+
+ ```
+ size_t i = 0;
+ for (; i + N <= count; i += N) LoopBody(d, i, 0);
+ for (; i < count; ++i) LoopBody(CappedTag(), i, 0);
+ ```
+ The template parameter and second function arguments are again not needed.
+
+ This avoids duplicating code, and is reasonable if `count` is large.
+ If `count` is small, the second loop may be slower than the next option.
+
+* Process whole vectors as above, followed by a single call to a modified
+ `LoopBody` with masking:
+
+ ```
+ size_t i = 0;
+ for (; i + N <= count; i += N) {
+ LoopBody(d, i, 0);
+ }
+ if (i < count) {
+ LoopBody(d, i, count - i);
+ }
+ ```
+ Now the template parameter and third function argument can be used inside
+ `LoopBody` to non-atomically 'blend' the first `num_remaining` lanes of `v`
+ with the previous contents of memory at subsequent locations:
+ `BlendedStore(v, FirstN(d, num_remaining), d, pointer);`. Similarly,
+ `MaskedLoad(FirstN(d, num_remaining), d, pointer)` loads the first
+ `num_remaining` elements and returns zero in other lanes.
+
+ This is a good default when it is infeasible to ensure vectors are padded,
+ but is only safe `#if !HWY_MEM_OPS_MIGHT_FAULT`!
+ In contrast to the scalar loop, only a single final iteration is needed.
+ The increased code size from two loop bodies is expected to be worthwhile
+ because it avoids the cost of masking in all but the final iteration.
+
+## Additional resources
+
+* [Highway introduction (slides)](g3doc/highway_intro.pdf)
+* [Overview of instructions per operation on different architectures](g3doc/instruction_matrix.pdf)
+* [Design philosophy and comparison](g3doc/design_philosophy.md)
+* [Implementation details](g3doc/impl_details.md)
+
+## Acknowledgments
+
+We have used [farm-sve](https://gitlab.inria.fr/bramas/farm-sve) by Berenger
+Bramas; it has proved useful for checking the SVE port on an x86 development
+machine.
+
+This is not an officially supported Google product.
+Contact: janwas@google.com
diff --git a/lib/highway/cmake/FindAtomics.cmake b/lib/highway/cmake/FindAtomics.cmake
new file mode 100644
index 00000000000..e866b73fac3
--- /dev/null
+++ b/lib/highway/cmake/FindAtomics.cmake
@@ -0,0 +1,56 @@
+# Original issue:
+# * https://gitlab.kitware.com/cmake/cmake/-/issues/23021#note_1098733
+#
+# For reference:
+# * https://gcc.gnu.org/wiki/Atomic/GCCMM
+#
+# riscv64 specific:
+# * https://lists.debian.org/debian-riscv/2022/01/msg00009.html
+#
+# ATOMICS_FOUND - system has c++ atomics
+# ATOMICS_LIBRARIES - libraries needed to use c++ atomics
+
+include(CheckCXXSourceCompiles)
+
+# RISC-V only has 32-bit and 64-bit atomic instructions. GCC is supposed
+# to convert smaller atomics to those larger ones via masking and
+# shifting like LLVM, but it’s a known bug that it does not. This means
+# anything that wants to use atomics on 1-byte or 2-byte types needs
+# -latomic, but not 4-byte or 8-byte (though it does no harm).
+set(atomic_code
+ "
+ #include
+ #include
+ std::atomic n8 (0); // riscv64
+ std::atomic n64 (0); // armel, mipsel, powerpc
+ int main() {
+ ++n8;
+ ++n64;
+ return 0;
+ }")
+
+# https://gitlab.kitware.com/cmake/cmake/-/issues/24063
+set(CMAKE_CXX_STANDARD 11)
+check_cxx_source_compiles("${atomic_code}" ATOMICS_LOCK_FREE_INSTRUCTIONS)
+
+if(ATOMICS_LOCK_FREE_INSTRUCTIONS)
+ set(ATOMICS_FOUND TRUE)
+ set(ATOMICS_LIBRARIES)
+else()
+ set(CMAKE_REQUIRED_LIBRARIES "-latomic")
+ check_cxx_source_compiles("${atomic_code}" ATOMICS_IN_LIBRARY)
+ set(CMAKE_REQUIRED_LIBRARIES)
+ if(ATOMICS_IN_LIBRARY)
+ set(ATOMICS_LIBRARY atomic)
+ include(FindPackageHandleStandardArgs)
+ find_package_handle_standard_args(Atomics DEFAULT_MSG ATOMICS_LIBRARY)
+ set(ATOMICS_LIBRARIES ${ATOMICS_LIBRARY})
+ unset(ATOMICS_LIBRARY)
+ else()
+ if(Atomics_FIND_REQUIRED)
+ message(FATAL_ERROR "Neither lock free instructions nor -latomic found.")
+ endif()
+ endif()
+endif()
+unset(atomic_code)
+unset(CMAKE_CXX_STANDARD)
diff --git a/lib/highway/hwy/abort.cc b/lib/highway/hwy/abort.cc
new file mode 100644
index 00000000000..a67819bbd35
--- /dev/null
+++ b/lib/highway/hwy/abort.cc
@@ -0,0 +1,117 @@
+// Copyright 2019 Google LLC
+// Copyright 2024 Arm Limited and/or its affiliates
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "hwy/abort.h"
+
+#include
+#include
+#include
+
+#include
+#include
+
+#include "hwy/base.h"
+
+#if HWY_IS_ASAN || HWY_IS_MSAN || HWY_IS_TSAN
+#include "sanitizer/common_interface_defs.h" // __sanitizer_print_stack_trace
+#endif
+
+namespace hwy {
+
+namespace {
+
+std::atomic& AtomicWarnFunc() {
+ static std::atomic func;
+ return func;
+}
+
+std::atomic& AtomicAbortFunc() {
+ static std::atomic func;
+ return func;
+}
+
+std::string GetBaseName(std::string const& file_name) {
+ auto last_slash = file_name.find_last_of("/\\");
+ return file_name.substr(last_slash + 1);
+}
+
+} // namespace
+
+// Returning a reference is unfortunately incompatible with `std::atomic`, which
+// is required to safely implement `SetWarnFunc`. As a workaround, we store a
+// copy here, update it when called, and return a reference to the copy. This
+// has the added benefit of protecting the actual pointer from modification.
+HWY_DLLEXPORT WarnFunc& GetWarnFunc() {
+ static WarnFunc func;
+ func = AtomicWarnFunc().load();
+ return func;
+}
+
+HWY_DLLEXPORT AbortFunc& GetAbortFunc() {
+ static AbortFunc func;
+ func = AtomicAbortFunc().load();
+ return func;
+}
+
+HWY_DLLEXPORT WarnFunc SetWarnFunc(WarnFunc func) {
+ return AtomicWarnFunc().exchange(func);
+}
+
+HWY_DLLEXPORT AbortFunc SetAbortFunc(AbortFunc func) {
+ return AtomicAbortFunc().exchange(func);
+}
+
+HWY_DLLEXPORT void HWY_FORMAT(3, 4)
+ Warn(const char* file, int line, const char* format, ...) {
+ char buf[800];
+ va_list args;
+ va_start(args, format);
+ vsnprintf(buf, sizeof(buf), format, args);
+ va_end(args);
+
+ WarnFunc handler = AtomicWarnFunc().load();
+ if (handler != nullptr) {
+ handler(file, line, buf);
+ } else {
+ fprintf(stderr, "Warn at %s:%d: %s\n", GetBaseName(file).data(), line, buf);
+ }
+}
+
+HWY_DLLEXPORT HWY_NORETURN void HWY_FORMAT(3, 4)
+ Abort(const char* file, int line, const char* format, ...) {
+ char buf[800];
+ va_list args;
+ va_start(args, format);
+ vsnprintf(buf, sizeof(buf), format, args);
+ va_end(args);
+
+ AbortFunc handler = AtomicAbortFunc().load();
+ if (handler != nullptr) {
+ handler(file, line, buf);
+ } else {
+ fprintf(stderr, "Abort at %s:%d: %s\n", GetBaseName(file).data(), line,
+ buf);
+ }
+
+// If compiled with any sanitizer, they can also print a stack trace.
+#if HWY_IS_ASAN || HWY_IS_MSAN || HWY_IS_TSAN
+ __sanitizer_print_stack_trace();
+#endif // HWY_IS_*
+ fflush(stderr);
+
+// Now terminate the program:
+#if HWY_ARCH_RISCV
+ exit(1); // trap/abort just freeze Spike.
+#elif HWY_IS_DEBUG_BUILD && !HWY_COMPILER_MSVC && !HWY_ARCH_ARM
+ // Facilitates breaking into a debugger, but don't use this in non-debug
+ // builds because it looks like "illegal instruction", which is misleading.
+ // Also does not work on Arm.
+ __builtin_trap();
+#else
+ abort(); // Compile error without this due to HWY_NORETURN.
+#endif
+}
+
+} // namespace hwy
diff --git a/lib/highway/hwy/abort.h b/lib/highway/hwy/abort.h
new file mode 100644
index 00000000000..931e9780504
--- /dev/null
+++ b/lib/highway/hwy/abort.h
@@ -0,0 +1,11 @@
+// Copyright 2024 Arm Limited and/or its affiliates
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: BSD-3-Clause
+
+#ifndef HIGHWAY_HWY_ABORT_H_
+#define HIGHWAY_HWY_ABORT_H_
+
+// Empty header for compatibility.
+// All Abort/Warn functionalities are in base.h.
+
+#endif // HIGHWAY_HWY_ABORT_H_
diff --git a/lib/highway/hwy/aligned_allocator.cc b/lib/highway/hwy/aligned_allocator.cc
new file mode 100644
index 00000000000..e857b2288fb
--- /dev/null
+++ b/lib/highway/hwy/aligned_allocator.cc
@@ -0,0 +1,156 @@
+// Copyright 2019 Google LLC
+// SPDX-License-Identifier: Apache-2.0
+//
+// 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.
+
+#include "hwy/aligned_allocator.h"
+
+#include
+#include
+#include // malloc
+
+#include
+#include
+
+#include "hwy/base.h"
+
+namespace hwy {
+namespace {
+
+#if HWY_ARCH_RISCV && defined(__riscv_v_intrinsic) && \
+ __riscv_v_intrinsic >= 11000
+// Not actually an upper bound on the size, but this value prevents crossing a
+// 4K boundary (relevant on Andes).
+constexpr size_t kAlignment = HWY_MAX(HWY_ALIGNMENT, 4096);
+#else
+constexpr size_t kAlignment = HWY_ALIGNMENT;
+#endif
+
+#if HWY_ARCH_X86
+// On x86, aliasing can only occur at multiples of 2K. To reduce the chance of
+// allocations being equal mod 2K, we round up to kAlias and add a cyclic
+// offset which is a multiple of kAlignment. Rounding up to only 1K decreases
+// the number of alias-free allocations, but also wastes less memory.
+constexpr size_t kAlias = HWY_MAX(kAlignment, 1024);
+#else
+constexpr size_t kAlias = kAlignment;
+#endif
+
+#pragma pack(push, 1)
+struct AllocationHeader {
+ void* allocated;
+ size_t payload_size;
+};
+#pragma pack(pop)
+
+// Returns a 'random' (cyclical) offset for AllocateAlignedBytes.
+size_t NextAlignedOffset() {
+ static std::atomic next{0};
+ static_assert(kAlias % kAlignment == 0, "kAlias must be a multiple");
+ constexpr size_t kGroups = kAlias / kAlignment;
+ const size_t group = next.fetch_add(1, std::memory_order_relaxed) % kGroups;
+ const size_t offset = kAlignment * group;
+ HWY_DASSERT((offset % kAlignment == 0) && offset <= kAlias);
+ return offset;
+}
+
+} // namespace
+
+HWY_DLLEXPORT void* AllocateAlignedBytes(const size_t payload_size,
+ AllocPtr alloc_ptr, void* opaque_ptr) {
+ HWY_ASSERT(payload_size != 0); // likely a bug in caller
+ if (payload_size >= std::numeric_limits::max() / 2) {
+ HWY_DASSERT(false && "payload_size too large");
+ return nullptr;
+ }
+
+ size_t offset = NextAlignedOffset();
+
+ // What: | misalign | unused | AllocationHeader |payload
+ // Size: |<= kAlias | offset |payload_size
+ // ^allocated.^aligned.^header............^payload
+ // The header must immediately precede payload, which must remain aligned.
+ // To avoid wasting space, the header resides at the end of `unused`,
+ // which therefore cannot be empty (offset == 0).
+ if (offset == 0) {
+ offset = RoundUpTo(sizeof(AllocationHeader), kAlignment);
+ }
+
+ const size_t allocated_size = kAlias + offset + payload_size;
+ void* allocated;
+ if (alloc_ptr == nullptr) {
+ allocated = malloc(allocated_size);
+ } else {
+ allocated = (*alloc_ptr)(opaque_ptr, allocated_size);
+ }
+ if (allocated == nullptr) return nullptr;
+ // Always round up even if already aligned - we already asked for kAlias
+ // extra bytes and there's no way to give them back.
+ uintptr_t aligned = reinterpret_cast(allocated) + kAlias;
+ static_assert((kAlias & (kAlias - 1)) == 0, "kAlias must be a power of 2");
+ static_assert(kAlias >= kAlignment, "Cannot align to more than kAlias");
+ aligned &= ~(kAlias - 1);
+
+ const uintptr_t payload = aligned + offset; // still aligned
+ HWY_DASSERT(payload % kAlignment == 0);
+
+ // Stash `allocated` and payload_size inside header for FreeAlignedBytes().
+ // The allocated_size can be reconstructed from the payload_size.
+ AllocationHeader* header = reinterpret_cast(payload) - 1;
+ HWY_DASSERT(reinterpret_cast(header) >= aligned);
+ header->allocated = allocated;
+ header->payload_size = payload_size;
+
+ return HWY_ASSUME_ALIGNED(reinterpret_cast(payload), kAlignment);
+}
+
+HWY_DLLEXPORT void FreeAlignedBytes(const void* aligned_pointer,
+ FreePtr free_ptr, void* opaque_ptr) {
+ if (aligned_pointer == nullptr) return;
+
+ const uintptr_t payload = reinterpret_cast(aligned_pointer);
+ HWY_DASSERT(payload % kAlignment == 0);
+ const AllocationHeader* header =
+ reinterpret_cast(payload) - 1;
+
+ if (free_ptr == nullptr) {
+ free(header->allocated);
+ } else {
+ (*free_ptr)(opaque_ptr, header->allocated);
+ }
+}
+
+// static
+HWY_DLLEXPORT void AlignedDeleter::DeleteAlignedArray(void* aligned_pointer,
+ FreePtr free_ptr,
+ void* opaque_ptr,
+ ArrayDeleter deleter) {
+ if (aligned_pointer == nullptr) return;
+
+ const uintptr_t payload = reinterpret_cast(aligned_pointer);
+ HWY_DASSERT(payload % kAlignment == 0);
+ const AllocationHeader* header =
+ reinterpret_cast(payload) - 1;
+
+ if (deleter) {
+ (*deleter)(aligned_pointer, header->payload_size);
+ }
+
+ if (free_ptr == nullptr) {
+ free(header->allocated);
+ } else {
+ (*free_ptr)(opaque_ptr, header->allocated);
+ }
+}
+
+} // namespace hwy
diff --git a/lib/highway/hwy/aligned_allocator.h b/lib/highway/hwy/aligned_allocator.h
new file mode 100644
index 00000000000..a682bdbac5e
--- /dev/null
+++ b/lib/highway/hwy/aligned_allocator.h
@@ -0,0 +1,469 @@
+// Copyright 2020 Google LLC
+// SPDX-License-Identifier: Apache-2.0
+//
+// 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 HIGHWAY_HWY_ALIGNED_ALLOCATOR_H_
+#define HIGHWAY_HWY_ALIGNED_ALLOCATOR_H_
+
+// Memory allocator with support for alignment and offsets.
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include